From 4830ba24aef5b793a81214c3c76303876af1a16f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Aug 2018 09:53:54 +1200 Subject: [PATCH 001/276] Highlight tablet when it is near-grabbable --- .../controllers/controllerDispatcher.js | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 7a916392b9..b1b41ded04 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -37,6 +37,20 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); PROFILE = true; } + var TABLET_GRABBABLE_SELECTION_NAME = "tabletGrabbableSelection"; + var TABLET_GRABBABLE_SELECTION_STYLE = { + outlineUnoccludedColor: { red: 0, green: 180, blue: 239 }, // #00b4ef + outlineUnoccludedAlpha: 1, + outlineOccludedColor: { red: 0, green: 0, blue: 0 }, + outlineOccludedAlpha: 0, + fillUnoccludedColor: { red: 0, green: 0, blue: 0 }, + fillUnoccludedAlpha: 0, + fillOccludedColor: { red: 0, green: 0, blue: 0 }, + fillOccludedAlpha: 0, + outlineWidth: 2, + isOutlineSmooth: false + }; + function ControllerDispatcher() { var _this = this; this.lastInterval = Date.now(); @@ -168,6 +182,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS); }; + this.isTabletNearGrabbable = false; + this.updateInternal = function () { if (PROFILE) { Script.beginProfileRange("dispatch.pre"); @@ -206,6 +222,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // find 3d overlays near each hand var nearbyOverlayIDs = []; + var isTabletNearGrabbable = false; var h; for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { if (controllerLocations[h].valid) { @@ -218,12 +235,26 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var bDistance = Vec3.distance(bPosition, controllerLocations[h].position); return aDistance - bDistance; }); + if (HMD.tabletID && nearbyOverlays.indexOf(HMD.tabletID) !== -1) { + isTabletNearGrabbable = true; + } nearbyOverlayIDs.push(nearbyOverlays); } else { nearbyOverlayIDs.push([]); } } + // Highlight tablet if it is near-grabbable. + if (isTabletNearGrabbable !== _this.isTabletNearGrabbable) { + if (isTabletNearGrabbable) { + Selection.addToSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME, "overlay", HMD.tabletID); + } else { + Selection.removeFromSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME, "overlay", HMD.tabletID); + } + _this.isTabletNearGrabbable = isTabletNearGrabbable; + } + + // find entities near each hand var nearbyEntityProperties = [[], []]; var nearbyEntityPropertiesByID = {}; @@ -485,6 +516,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Selection.disableListHighlight(DISPATCHER_HOVERING_LIST); }; } + function mouseReleaseOnOverlay(overlayID, event) { if (HMD.homeButtonID && overlayID === HMD.homeButtonID && event.button === "Primary") { Messages.sendLocalMessage("home", overlayID); @@ -503,12 +535,29 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } } + Overlays.mouseReleaseOnOverlay.connect(mouseReleaseOnOverlay); Overlays.mousePressOnOverlay.connect(mousePress); Entities.mousePressOnEntity.connect(mousePress); + + function onDisplayModeChanged() { + if (HMD.active) { + Selection.enableListHighlight(TABLET_GRABBABLE_SELECTION_NAME, TABLET_GRABBABLE_SELECTION_STYLE); + } else { + Selection.disableListHighlight(TABLET_GRABBABLE_SELECTION_NAME); + Selection.clearSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME); + } + } + + HMD.displayModeChanged.connect(onDisplayModeChanged); + HMD.mountedChanged.connect(onDisplayModeChanged); + var controllerDispatcher = new ControllerDispatcher(); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.messageReceived.connect(controllerDispatcher.handleHandMessage); - Script.scriptEnding.connect(controllerDispatcher.cleanup); + Script.scriptEnding.connect(function () { + controllerDispatcher.cleanup(); + Selection.disableListHighlight(TABLET_GRABBABLE_SELECTION_NAME); + }); Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS); }()); From 9267919d7fbe6e2daa3a3631b7d7f7146ba092ed Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Wed, 29 Aug 2018 16:49:27 -0700 Subject: [PATCH 002/276] Resorting Developer menu organiztion --- interface/src/Menu.cpp | 85 +++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a7d7dcf8b3..62acb08d44 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -285,7 +285,50 @@ Menu::Menu() { // Developer menu ---------------------------------- MenuWrapper* developerMenu = addMenu("Developer", "Developer"); + + // Developer > Scripting >>> + MenuWrapper* scriptingOptionsMenu = developerMenu->addMenu("Scripting"); + + // Developer > Scripting > Console... + addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, + DependencyManager::get().data(), + SLOT(toggleConsole()), + QAction::NoRole, + UNSPECIFIED_POSITION); + // Developer > Scripting > API Debugger + action = addActionToQMenuAndActionHash(scriptingOptionsMenu, "API Debugger"); + connect(action, &QAction::triggered, [] { + QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); + defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); + DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); + }); + + // Developer > Scripting > Log... + addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, + qApp, SLOT(toggleLogDialog())); + + // Developer > Scripting > Entity Script Server Log + auto essLogAction = addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::EntityScriptServerLog, 0, + qApp, SLOT(toggleEntityScriptServerLogDialog())); + { + auto nodeList = DependencyManager::get(); + QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { + auto nodeList = DependencyManager::get(); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + }); + essLogAction->setEnabled(nodeList->getThisNodeCanRez()); + } + + // Developer > Scripting > Script Log (HMD friendly)... + addActionToQMenuAndActionHash(scriptingOptionsMenu, "Script Log (HMD friendly)...", Qt::NoButton, + qApp, SLOT(showScriptLogs())); + + // Developer > Scripting > Verbose Logging + addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::VerboseLogging, 0, false, + qApp, SLOT(updateVerboseLogging())); + + // Developer > UI >>> MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI"); action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0, @@ -691,10 +734,11 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraints, 0, false, qApp, SLOT(setShowBulletConstraints(bool))); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletConstraintLimits, 0, false, qApp, SLOT(setShowBulletConstraintLimits(bool))); - // Developer > Display Crash Options - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); // Developer > Crash >>> MenuWrapper* crashMenu = developerMenu->addMenu("Crash"); + + // Developer > Crash > Display Crash Options + addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true); addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication())); addActionToQMenuAndActionHash(crashMenu, MenuOption::UnresponsiveInterface, 0, qApp, SLOT(unresponsiveApplication())); @@ -729,7 +773,7 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded); connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); }); - // Developer > Stats + // Developer > Show Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); // Settings > Enable Speech Control API @@ -744,41 +788,6 @@ Menu::Menu() { connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - // console - addActionToQMenuAndActionHash(developerMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, - DependencyManager::get().data(), - SLOT(toggleConsole()), - QAction::NoRole, - UNSPECIFIED_POSITION); - - // Developer > API Debugger - action = addActionToQMenuAndActionHash(developerMenu, "API Debugger"); - connect(action, &QAction::triggered, [] { - QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); - DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); - }); - - // Developer > Log... - addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, - qApp, SLOT(toggleLogDialog())); - auto essLogAction = addActionToQMenuAndActionHash(developerMenu, MenuOption::EntityScriptServerLog, 0, - qApp, SLOT(toggleEntityScriptServerLogDialog())); - { - auto nodeList = DependencyManager::get(); - QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] { - auto nodeList = DependencyManager::get(); - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - }); - essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - } - - addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton, - qApp, SLOT(showScriptLogs())); - - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::VerboseLogging, 0, false, - qApp, SLOT(updateVerboseLogging())); - // Developer > Show Overlays addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true); From 7bc860cbc9806f87c38cdfd51307df112831e9b3 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 30 Aug 2018 11:42:26 -0700 Subject: [PATCH 003/276] Move Debug defaultscripts.js to Scripting menu --- scripts/defaultScripts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index b275660c0f..1ac84cd29e 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -39,7 +39,7 @@ var DEFAULT_SCRIPTS_SEPARATE = [ ]; // add a menu item for debugging -var MENU_CATEGORY = "Developer"; +var MENU_CATEGORY = "Developer > Scripting"; var MENU_ITEM = "Debug defaultScripts.js"; var SETTINGS_KEY = '_debugDefaultScriptsIsChecked'; From 1233c2ec0d8ed90709f81d992921e9b107dbf3e1 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Thu, 30 Aug 2018 14:46:53 -0700 Subject: [PATCH 004/276] Moving root settings into preferred submenus --- interface/src/Menu.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 62acb08d44..e0e16f449f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -328,15 +328,32 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::VerboseLogging, 0, false, qApp, SLOT(updateVerboseLogging())); + // Developer > Scripting > Enable Speech Control API +#if defined(Q_OS_MAC) || defined(Q_OS_WIN) + auto speechRecognizer = DependencyManager::get(); + QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::ControlWithSpeech, + Qt::CTRL | Qt::SHIFT | Qt::Key_C, + speechRecognizer->getEnabled(), + speechRecognizer.data(), + SLOT(setEnabled(bool)), + UNSPECIFIED_POSITION); + connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); +#endif // Developer > UI >>> MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI"); action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0, qApp->getDesktopTabletBecomesToolbarSetting()); + + // Developer > UI > Show Overlays + addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::Overlays, 0, true); + + // Developer > UI > Desktop Tablet Becomes Toolbar connect(action, &QAction::triggered, [action] { qApp->setDesktopTabletBecomesToolbarSetting(action->isChecked()); }); - + + // Developer > UI > HMD Tablet Becomes Toolbar action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::HMDTabletToToolbar, 0, qApp->getHmdTabletBecomesToolbarSetting()); connect(action, &QAction::triggered, [action] { @@ -776,21 +793,6 @@ Menu::Menu() { // Developer > Show Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); - // Settings > Enable Speech Control API -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) - auto speechRecognizer = DependencyManager::get(); - QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::ControlWithSpeech, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, - speechRecognizer->getEnabled(), - speechRecognizer.data(), - SLOT(setEnabled(bool)), - UNSPECIFIED_POSITION); - connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); -#endif - - // Developer > Show Overlays - addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true); - #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); QAction* backAction = addActionToQMenuAndActionHash(navigateMenu, MenuOption::Back, 0, addressManager.data(), SLOT(goBack())); From f84f3b20cdf79c2d75ff4703808838912a4c44d9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 31 Aug 2018 10:25:12 +1200 Subject: [PATCH 005/276] Disable near and far lasers when tablet is grabbable --- .../controllerModules/farActionGrabEntity.js | 3 +- .../farActionGrabEntityDynOnly.js | 3 +- .../controllerModules/farParentGrabEntity.js | 3 +- .../controllerModules/nearTabletHighlight.js | 67 +++++++++++++++++++ .../controllerModules/webSurfaceLaserInput.js | 7 ++ .../system/controllers/controllerScripts.js | 3 +- 6 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 scripts/system/controllers/controllerModules/nearTabletHighlight.js diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 5e798ed680..b5141699f9 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -420,7 +420,8 @@ Script.include("/~/system/libraries/Xform.js"); this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" + this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay", + this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight" ]; var nearGrabReadiness = []; diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js index 78abcb9b20..4fab0098b9 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js @@ -396,7 +396,8 @@ Script.include("/~/system/libraries/Xform.js"); this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" + this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay", + this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight" ]; var nearGrabReadiness = []; diff --git a/scripts/system/controllers/controllerModules/farParentGrabEntity.js b/scripts/system/controllers/controllerModules/farParentGrabEntity.js index a9ec246a32..d58cbb9907 100644 --- a/scripts/system/controllers/controllerModules/farParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farParentGrabEntity.js @@ -442,7 +442,8 @@ Script.include("/~/system/libraries/Xform.js"); this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay" + this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay", + this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight" ]; var nearGrabReadiness = []; diff --git a/scripts/system/controllers/controllerModules/nearTabletHighlight.js b/scripts/system/controllers/controllerModules/nearTabletHighlight.js new file mode 100644 index 0000000000..5f4dac232e --- /dev/null +++ b/scripts/system/controllers/controllerModules/nearTabletHighlight.js @@ -0,0 +1,67 @@ +// +// nearTabletHighlight.js +// +// Highlight the tablet if a hand is near enough to grab it. +// +// Created by David Rowe on 28 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 LEFT_HAND, RIGHT_HAND, makeDispatcherModuleParameters, makeRunningValues, enableDispatcherModule, + * disableDispatcherModule */ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); + +(function () { + + "use strict"; + + function NearTabletHighlight(hand) { + this.hand = hand; + + this.parameters = makeDispatcherModuleParameters( + 95, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100 + ); + + this.isNearTablet = function (controllerData) { + return HMD.tabletID && controllerData.nearbyOverlayIDs[this.hand].indexOf(HMD.tabletID) !== -1; + }; + + this.isReady = function (controllerData) { + if (this.isNearTablet(controllerData)) { + return makeRunningValues(true, [], []); + } + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + if (!this.isNearTablet(controllerData)) { + return makeRunningValues(false, [], []); + } + + if (controllerData.triggerClicks[this.hand]) { + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + } + + var leftNearTabletHighlight = new NearTabletHighlight(LEFT_HAND); + var rightNearTabletHighlight = new NearTabletHighlight(RIGHT_HAND); + enableDispatcherModule("LeftNearTabletHighlight", leftNearTabletHighlight); + enableDispatcherModule("RightNearTabletHighlight", rightNearTabletHighlight); + + function cleanUp() { + disableDispatcherModule("LeftNearTabletHighlight"); + disableDispatcherModule("RightNearTabletHighlight"); + } + Script.scriptEnding.connect(cleanUp); + +}()); diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index a2fe0bfcd4..7ed9fd68b5 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -43,6 +43,13 @@ Script.include("/~/system/libraries/controllers.js"); } } } + + var nearTabletHighlightModule = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"); + if (nearTabletHighlightModule) { + return nearTabletHighlightModule.isNearTablet(controllerData); + } + return false; }; diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 6899577de2..8011c2b644 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -35,7 +35,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/scaleEntity.js", "controllerModules/highlightNearbyEntities.js", "controllerModules/nearGrabHyperLinkEntity.js", - "controllerModules/mouseHighlightEntities.js" + "controllerModules/mouseHighlightEntities.js", + "controllerModules/nearTabletHighlight.js" ]; if (Settings.getValue("useFarGrabJoints", false)) { From 9203bbc5d8ec265169d1293287614837c6e2043d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 31 Aug 2018 11:02:36 +1200 Subject: [PATCH 006/276] Don't highlight tablet when it is grabbed --- .../controllers/controllerDispatcher.js | 48 +--------------- .../controllerModules/nearTabletHighlight.js | 55 ++++++++++++++++++- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index b1b41ded04..a707050a9d 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -37,20 +37,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); PROFILE = true; } - var TABLET_GRABBABLE_SELECTION_NAME = "tabletGrabbableSelection"; - var TABLET_GRABBABLE_SELECTION_STYLE = { - outlineUnoccludedColor: { red: 0, green: 180, blue: 239 }, // #00b4ef - outlineUnoccludedAlpha: 1, - outlineOccludedColor: { red: 0, green: 0, blue: 0 }, - outlineOccludedAlpha: 0, - fillUnoccludedColor: { red: 0, green: 0, blue: 0 }, - fillUnoccludedAlpha: 0, - fillOccludedColor: { red: 0, green: 0, blue: 0 }, - fillOccludedAlpha: 0, - outlineWidth: 2, - isOutlineSmooth: false - }; - function ControllerDispatcher() { var _this = this; this.lastInterval = Date.now(); @@ -182,8 +168,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS); }; - this.isTabletNearGrabbable = false; - this.updateInternal = function () { if (PROFILE) { Script.beginProfileRange("dispatch.pre"); @@ -222,7 +206,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // find 3d overlays near each hand var nearbyOverlayIDs = []; - var isTabletNearGrabbable = false; var h; for (h = LEFT_HAND; h <= RIGHT_HAND; h++) { if (controllerLocations[h].valid) { @@ -235,26 +218,12 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var bDistance = Vec3.distance(bPosition, controllerLocations[h].position); return aDistance - bDistance; }); - if (HMD.tabletID && nearbyOverlays.indexOf(HMD.tabletID) !== -1) { - isTabletNearGrabbable = true; - } nearbyOverlayIDs.push(nearbyOverlays); } else { nearbyOverlayIDs.push([]); } } - // Highlight tablet if it is near-grabbable. - if (isTabletNearGrabbable !== _this.isTabletNearGrabbable) { - if (isTabletNearGrabbable) { - Selection.addToSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME, "overlay", HMD.tabletID); - } else { - Selection.removeFromSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME, "overlay", HMD.tabletID); - } - _this.isTabletNearGrabbable = isTabletNearGrabbable; - } - - // find entities near each hand var nearbyEntityProperties = [[], []]; var nearbyEntityPropertiesByID = {}; @@ -540,24 +509,9 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Overlays.mousePressOnOverlay.connect(mousePress); Entities.mousePressOnEntity.connect(mousePress); - function onDisplayModeChanged() { - if (HMD.active) { - Selection.enableListHighlight(TABLET_GRABBABLE_SELECTION_NAME, TABLET_GRABBABLE_SELECTION_STYLE); - } else { - Selection.disableListHighlight(TABLET_GRABBABLE_SELECTION_NAME); - Selection.clearSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME); - } - } - - HMD.displayModeChanged.connect(onDisplayModeChanged); - HMD.mountedChanged.connect(onDisplayModeChanged); - var controllerDispatcher = new ControllerDispatcher(); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.messageReceived.connect(controllerDispatcher.handleHandMessage); - Script.scriptEnding.connect(function () { - controllerDispatcher.cleanup(); - Selection.disableListHighlight(TABLET_GRABBABLE_SELECTION_NAME); - }); + Script.scriptEnding.connect(controllerDispatcher.cleanup); Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS); }()); diff --git a/scripts/system/controllers/controllerModules/nearTabletHighlight.js b/scripts/system/controllers/controllerModules/nearTabletHighlight.js index 5f4dac232e..d436bad458 100644 --- a/scripts/system/controllers/controllerModules/nearTabletHighlight.js +++ b/scripts/system/controllers/controllerModules/nearTabletHighlight.js @@ -1,7 +1,7 @@ // // nearTabletHighlight.js // -// Highlight the tablet if a hand is near enough to grab it. +// Highlight the tablet if a hand is near enough to grab it and it isn't grabbed. // // Created by David Rowe on 28 Aug 2018. // Copyright 2018 High Fidelity, Inc. @@ -19,6 +19,43 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); "use strict"; + var TABLET_GRABBABLE_SELECTION_NAME = "tabletGrabbableSelection"; + var TABLET_GRABBABLE_SELECTION_STYLE = { + outlineUnoccludedColor: { red: 0, green: 180, blue: 239 }, // #00b4ef + outlineUnoccludedAlpha: 1, + outlineOccludedColor: { red: 0, green: 0, blue: 0 }, + outlineOccludedAlpha: 0, + fillUnoccludedColor: { red: 0, green: 0, blue: 0 }, + fillUnoccludedAlpha: 0, + fillOccludedColor: { red: 0, green: 0, blue: 0 }, + fillOccludedAlpha: 0, + outlineWidth: 2, + isOutlineSmooth: false + }; + + var isTabletNearGrabbable = [false, false]; + var isTabletHighlighted = false; + + function setTabletNearGrabbable(hand, enabled) { + if (enabled === isTabletNearGrabbable[hand]) { + return; + } + + isTabletNearGrabbable[hand] = enabled; + + if (isTabletNearGrabbable[LEFT_HAND] || isTabletNearGrabbable[RIGHT_HAND]) { + if (!isTabletHighlighted) { + Selection.addToSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME, "overlay", HMD.tabletID); + isTabletHighlighted = true; + } + } else { + if (isTabletHighlighted) { + Selection.removeFromSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME, "overlay", HMD.tabletID); + isTabletHighlighted = false; + } + } + } + function NearTabletHighlight(hand) { this.hand = hand; @@ -37,18 +74,22 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (this.isNearTablet(controllerData)) { return makeRunningValues(true, [], []); } + setTabletNearGrabbable(this.hand, false); return makeRunningValues(false, [], []); }; this.run = function (controllerData) { if (!this.isNearTablet(controllerData)) { + setTabletNearGrabbable(this.hand, false); return makeRunningValues(false, [], []); } if (controllerData.triggerClicks[this.hand]) { + setTabletNearGrabbable(this.hand, false); return makeRunningValues(false, [], []); } + setTabletNearGrabbable(this.hand, true); return makeRunningValues(true, [], []); }; } @@ -58,9 +99,21 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); enableDispatcherModule("LeftNearTabletHighlight", leftNearTabletHighlight); enableDispatcherModule("RightNearTabletHighlight", rightNearTabletHighlight); + function onDisplayModeChanged() { + if (HMD.active) { + Selection.enableListHighlight(TABLET_GRABBABLE_SELECTION_NAME, TABLET_GRABBABLE_SELECTION_STYLE); + } else { + Selection.disableListHighlight(TABLET_GRABBABLE_SELECTION_NAME); + Selection.clearSelectedItemsList(TABLET_GRABBABLE_SELECTION_NAME); + } + } + HMD.displayModeChanged.connect(onDisplayModeChanged); + HMD.mountedChanged.connect(onDisplayModeChanged); + function cleanUp() { disableDispatcherModule("LeftNearTabletHighlight"); disableDispatcherModule("RightNearTabletHighlight"); + Selection.disableListHighlight(TABLET_GRABBABLE_SELECTION_NAME); } Script.scriptEnding.connect(cleanUp); From 90e98f1608e1a2bad82ada56738fabcfbc28c2a9 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Fri, 31 Aug 2018 09:55:46 -0700 Subject: [PATCH 007/276] Moving Log to root Developer menu --- interface/src/Menu.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e0e16f449f..915ef9e400 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -303,11 +303,7 @@ Menu::Menu() { defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); }); - - // Developer > Scripting > Log... - addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, - qApp, SLOT(toggleLogDialog())); - + // Developer > Scripting > Entity Script Server Log auto essLogAction = addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::EntityScriptServerLog, 0, qApp, SLOT(toggleEntityScriptServerLogDialog())); @@ -792,6 +788,10 @@ Menu::Menu() { // Developer > Show Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + + // Developer > Log + addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, + qApp, SLOT(toggleLogDialog())); #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); From daae3f50c8080b1e03b6e81155d43c5efcc12cd4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Sep 2018 09:09:30 +1200 Subject: [PATCH 008/276] Increase tablet highlight outline width --- .../system/controllers/controllerModules/nearTabletHighlight.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/nearTabletHighlight.js b/scripts/system/controllers/controllerModules/nearTabletHighlight.js index d436bad458..7738f48561 100644 --- a/scripts/system/controllers/controllerModules/nearTabletHighlight.js +++ b/scripts/system/controllers/controllerModules/nearTabletHighlight.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); fillUnoccludedAlpha: 0, fillOccludedColor: { red: 0, green: 0, blue: 0 }, fillOccludedAlpha: 0, - outlineWidth: 2, + outlineWidth: 4, isOutlineSmooth: false }; From f23d93add3f0f9489e2a2cdaf65cabdf47e79cf6 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Sep 2018 09:17:00 +1200 Subject: [PATCH 009/276] Fix grabbing tablet with grip not working --- .../system/controllers/controllerModules/nearTabletHighlight.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/nearTabletHighlight.js b/scripts/system/controllers/controllerModules/nearTabletHighlight.js index 7738f48561..2b02bf3aed 100644 --- a/scripts/system/controllers/controllerModules/nearTabletHighlight.js +++ b/scripts/system/controllers/controllerModules/nearTabletHighlight.js @@ -84,7 +84,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); return makeRunningValues(false, [], []); } - if (controllerData.triggerClicks[this.hand]) { + if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand]) { setTabletNearGrabbable(this.hand, false); return makeRunningValues(false, [], []); } From 16a94f827df1c4406f0750c1b1e62fdac84bcf67 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 11 Sep 2018 09:51:28 +1200 Subject: [PATCH 010/276] Fix tablet highlight not working with stylus --- .../system/controllers/controllerModules/stylusInput.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/stylusInput.js b/scripts/system/controllers/controllerModules/stylusInput.js index a512fd89db..a60ea596e9 100644 --- a/scripts/system/controllers/controllerModules/stylusInput.js +++ b/scripts/system/controllers/controllerModules/stylusInput.js @@ -70,7 +70,13 @@ Script.include("/~/system/libraries/controllers.js"); var farGrabModuleName = this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"; var farGrabModule = getEnabledModuleByName(farGrabModuleName); var farGrabModuleReady = farGrabModule ? farGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); - return grabOverlayModuleReady.active || farGrabModuleReady.active || grabEntityModuleReady.active; + var nearTabletHighlightModuleName = + this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"; + var nearTabletHighlightModule = getEnabledModuleByName(nearTabletHighlightModuleName); + var nearTabletHighlightModuleReady = nearTabletHighlightModule + ? nearTabletHighlightModule.isReady(controllerData) : makeRunningValues(false, [], []); + return grabOverlayModuleReady.active || farGrabModuleReady.active || grabEntityModuleReady.active + || nearTabletHighlightModuleReady.active; }; this.overlayLaserActive = function(controllerData) { From 992b9f847ca9eb85adc5430fc4cddf959a96c581 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 11 Sep 2018 09:58:19 +1200 Subject: [PATCH 011/276] Reduce near grab distance of tablet so that stylus works on tablet --- scripts/system/controllers/controllerDispatcher.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index a707050a9d..69b1b27035 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -26,6 +26,7 @@ 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; @@ -211,6 +212,17 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); if (controllerLocations[h].valid) { var nearbyOverlays = Overlays.findOverlays(controllerLocations[h].position, NEAR_MAX_RADIUS * sensorScaleFactor); + + // Tablet must be within NEAR_TABLET_MAX_RADIUS in order to be grabbed. + var tabletIndex = nearbyOverlays.indexOf(HMD.tabletID); + if (tabletIndex !== -1) { + var closebyOverlays = + Overlays.findOverlays(controllerLocations[h].position, NEAR_TABLET_MAX_RADIUS * sensorScaleFactor); + if (tabletIndex !== -1 && closebyOverlays.indexOf(HMD.tabletID) === -1) { + nearbyOverlays.splice(tabletIndex, 1); + } + } + nearbyOverlays.sort(function (a, b) { var aPosition = Overlays.getProperty(a, "position"); var aDistance = Vec3.distance(aPosition, controllerLocations[h].position); From 6a1c76d14d7d5a189f70c7fc66ef313b8bca7054 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 14 Sep 2018 12:19:55 -0700 Subject: [PATCH 012/276] Only sort an estimated number of avatars --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 10 +++++++++- libraries/shared/src/PrioritySortUtil.h | 10 ++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 7368db0c31..e17c108775 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -244,6 +244,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // reset the internal state for correct random number distribution distribution.reset(); + // Base number to sort on number previously sent. + const int numToSendEst = std::max(nodeData->getNumAvatarsSentLastFrame() * 2, 20); + // reset the number of sent avatars nodeData->resetNumAvatarsSentLastFrame(); @@ -399,7 +402,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); - const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); + const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); for (const auto& sortedAvatar : sortedAvatarVector) { const Node* otherNode = sortedAvatar.getNode(); auto lastEncodeForOther = sortedAvatar.getTimestamp(); @@ -524,6 +527,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) remainingAvatars--; } + if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) { + qCWarning(avatars) << "More avatars sent than upper estimate" << nodeData->getNumAvatarsSentLastFrame() + << " / " << numToSendEst; + } + quint64 startPacketSending = usecTimestampNow(); // close the current packet so that we're always sending something diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 27f6b193ba..3a40fead71 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -66,8 +66,14 @@ namespace PrioritySortUtil { 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(); }); + const std::vector& getSortedVector(int numToSort = 0) { + if (numToSort == 0 || numToSort >= (int)_vector.size()) { + std::sort(_vector.begin(), _vector.end(), + [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + } else { + std::partial_sort(_vector.begin(), _vector.begin() + numToSort, _vector.end(), + [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); }); + } return _vector; } From ca300db410eae5a8fef6ab6143ea415e0a099dae Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 14 Sep 2018 18:24:08 -0700 Subject: [PATCH 013/276] Send only avatar data that will fit in packet (WIP) --- .../src/avatars/AvatarMixerSlave.cpp | 16 +- libraries/avatars/src/AvatarData.cpp | 222 ++++++++++-------- libraries/avatars/src/AvatarData.h | 2 +- 3 files changed, 137 insertions(+), 103 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index e17c108775..20d85a3d4d 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -460,13 +460,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool distanceAdjust = true; glm::vec3 viewerPosition = myPosition; - AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray + AvatarDataPacket::HasFlags hasFlagsOut = 0; // the result of the toByteArray bool dropFaceTracking = false; auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, - &lastSentJointsForOther); + &lastSentJointsForOther, maxAvatarDataBytes); auto endSerialize = chrono::high_resolution_clock::now(); _stats.toByteArrayElapsedTime += (quint64) chrono::duration_cast(endSerialize - startSerialize).count(); @@ -477,16 +477,16 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) dropFaceTracking = true; // first try dropping the facial data bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther, maxAvatarDataBytes); if (bytes.size() > maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "without facial data resulted in very large buffer of" << bytes.size() << "bytes - reducing to MinimumData"; bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther, maxAvatarDataBytes); - if (bytes.size() > maxAvatarDataBytes) { + if (bytes.size() > maxAvatarDataBytes, maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "MinimumData resulted in very large buffer of" << bytes.size() << "bytes - refusing to send avatar"; @@ -604,7 +604,7 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin QVector emptyLastJointSendData { otherAvatar->getJointCount() }; QByteArray avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, false, false, glm::vec3(0), nullptr); + flagsOut, false, false, glm::vec3(0), nullptr, 0); quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); @@ -630,14 +630,14 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr); + flagsOut, true, false, glm::vec3(0), nullptr, 0); if (avatarByteArray.size() > maxAvatarByteArraySize) { qCWarning(avatars) << "Replicated avatar data without facial data still too large for" << otherAvatar->getSessionUUID() << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::MinimumData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr); + flagsOut, true, false, glm::vec3(0), nullptr, 0); } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 834754e228..fdd5317bce 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -227,13 +227,13 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro _lastToByteArray = usecTimestampNow(); return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), hasFlagsOut, dropFaceTracking, false, glm::vec3(0), nullptr, - &_outboundDataRate); + 0, &_outboundDataRate); } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, - glm::vec3 viewerPosition, QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const { + AvatarDataPacket::HasFlags& itemFlags, bool dropFaceTracking, bool distanceAdjust, + glm::vec3 viewerPosition, QVector* sentJointDataOut, int maxDataSize, AvatarDataRate* outboundDataRateOut) const { bool cullSmallChanges = (dataDetail == CullSmallData); bool sendAll = (dataDetail == SendAllData); @@ -270,22 +270,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent auto parentID = getParentID(); - bool hasAvatarGlobalPosition = true; // always include global position - bool hasAvatarOrientation = false; - bool hasAvatarBoundingBox = false; - bool hasAvatarScale = false; - bool hasLookAtPosition = false; - bool hasAudioLoudness = false; - bool hasSensorToWorldMatrix = false; - bool hasAdditionalFlags = false; - - // local position, and parent info only apply to avatars that are parented. The local position - // and the parent info can change independently though, so we track their "changed since" - // separately - bool hasParentInfo = false; - bool hasAvatarLocalPosition = false; - - bool hasFaceTrackerInfo = false; bool hasJointData = false; bool hasJointDefaultPoseFlags = false; bool hasGrabJoints = false; @@ -294,80 +278,121 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent glm::mat4 rightFarGrabMatrix; glm::mat4 mouseFarGrabMatrix; - if (sendPALMinimum) { - hasAudioLoudness = true; - } else { - hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime); - hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime); - hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime); - hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime); - hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime); - hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime); - hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime); - hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime); - hasAvatarLocalPosition = hasParent() && (sendAll || - tranlationChangedSince(lastSentTime) || - parentInfoChangedSince(lastSentTime)); + // Leading flags, to indicate how much data is actually included in the packet... + AvatarDataPacket::HasFlags packetStateFlags = 0; - hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && - (sendAll || faceTrackerInfoChangedSince(lastSentTime)); - hasJointData = sendAll || !sendMinimum; - hasJointDefaultPoseFlags = hasJointData; - if (hasJointData) { - bool leftValid; - leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); - if (!leftValid) { - leftFarGrabMatrix = glm::mat4(); + if (itemFlags == 0) { + bool hasAvatarGlobalPosition = true; // always include global position + bool hasAvatarOrientation = false; + bool hasAvatarBoundingBox = false; + bool hasAvatarScale = false; + bool hasLookAtPosition = false; + bool hasAudioLoudness = false; + bool hasSensorToWorldMatrix = false; + bool hasAdditionalFlags = false; + + // local position, and parent info only apply to avatars that are parented. The local position + // and the parent info can change independently though, so we track their "changed since" + // separately + bool hasParentInfo = false; + bool hasAvatarLocalPosition = false; + + bool hasFaceTrackerInfo = false; + + if (sendPALMinimum) { + hasAudioLoudness = true; + } else { + hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime); + hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime); + hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime); + hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime); + hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime); + hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime); + hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime); + hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime); + hasAvatarLocalPosition = hasParent() && (sendAll || + tranlationChangedSince(lastSentTime) || + parentInfoChangedSince(lastSentTime)); + + hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && + (sendAll || faceTrackerInfoChangedSince(lastSentTime)); + hasJointData = sendAll || !sendMinimum; + hasJointDefaultPoseFlags = hasJointData; + if (hasJointData) { + bool leftValid; + leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); + if (!leftValid) { + leftFarGrabMatrix = glm::mat4(); + } + bool rightValid; + rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); + if (!rightValid) { + rightFarGrabMatrix = glm::mat4(); + } + bool mouseValid; + mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); + if (!mouseValid) { + mouseFarGrabMatrix = glm::mat4(); + } + hasGrabJoints = (leftValid || rightValid || mouseValid); } - bool rightValid; - rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); - if (!rightValid) { - rightFarGrabMatrix = glm::mat4(); - } - bool mouseValid; - mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); - if (!mouseValid) { - mouseFarGrabMatrix = glm::mat4(); - } - hasGrabJoints = (leftValid || rightValid || mouseValid); } + + packetStateFlags = + (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) + | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) + | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) + | (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0) + | (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0) + | (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0) + | (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0) + | (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0) + | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) + | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) + | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) + | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) + | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) + | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); + + } else { + packetStateFlags = itemFlags; } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + - (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + - (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size(), hasGrabJoints) : 0) + - (hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); + (packetStateFlags & AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + + (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA ? AvatarDataPacket::maxJointDataSize(_jointData.size(), true) : 0) + + (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); + + if (maxDataSize == 0) { + maxDataSize = (int)byteArraySize; + } QByteArray avatarDataByteArray((int)byteArraySize, 0); unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; - // Leading flags, to indicate how much data is actually included in the packet... - AvatarDataPacket::HasFlags packetStateFlags = - (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) - | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) - | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) - | (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0) - | (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0) - | (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0) - | (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0) - | (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0) - | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) - | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) - | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) - | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) - | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) - | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); - - memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); + unsigned char * packetFlagsLocation = destinationBuffer; destinationBuffer += sizeof(packetStateFlags); + if (itemFlags == 0) { + itemFlags = packetStateFlags; + } + + AvatarDataPacket::HasFlags includedFlags = 0; + + const unsigned char * packetEnd = destinationBuffer + maxDataSize; + #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); - if (hasAvatarGlobalPosition) { +// If we want an item and there's sufficient space: +#define IF_AVATAR_SPACE(flag, space) \ + if ((itemFlags & AvatarDataPacket::flag) && (packetEnd - destinationBuffer) >= (space) \ + && (includedFlags |= AvatarDataPacket::flag)) + + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { auto startSection = destinationBuffer; AVATAR_MEMCPY(_globalPosition); @@ -378,7 +403,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarBoundingBox) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_BOUNDING_BOX, sizeof _globalBoundingBoxDimensions + sizeof _globalBoundingBoxOffset) { auto startSection = destinationBuffer; AVATAR_MEMCPY(_globalBoundingBoxDimensions); AVATAR_MEMCPY(_globalBoundingBoxOffset); @@ -389,7 +414,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarOrientation) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_ORIENTATION, 6) { auto startSection = destinationBuffer; auto localOrientation = getOrientationOutbound(); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); @@ -400,7 +425,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarScale) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_SCALE, sizeof(AvatarDataPacket::AvatarScale)) { auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); auto scale = getDomainLimitedScale(); @@ -413,7 +438,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasLookAtPosition) { + IF_AVATAR_SPACE(PACKET_HAS_LOOK_AT_POSITION, sizeof(_headData->getLookAtPosition()) ) { auto startSection = destinationBuffer; AVATAR_MEMCPY(_headData->getLookAtPosition()); int numBytes = destinationBuffer - startSection; @@ -422,7 +447,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAudioLoudness) { + IF_AVATAR_SPACE(PACKET_HAS_AUDIO_LOUDNESS, sizeof(AvatarDataPacket::AudioLoudness)) { auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); data->audioLoudness = packFloatGainToByte(getAudioLoudness() / AUDIO_LOUDNESS_SCALE); @@ -434,7 +459,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasSensorToWorldMatrix) { + IF_AVATAR_SPACE(PACKET_HAS_SENSOR_TO_WORLD_MATRIX, sizeof(AvatarDataPacket::SensorToWorldMatrix)) { auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); @@ -452,7 +477,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAdditionalFlags) { + IF_AVATAR_SPACE(PACKET_HAS_ADDITIONAL_FLAGS, sizeof (uint16_t)) { auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); @@ -500,7 +525,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasParentInfo) { + IF_AVATAR_SPACE(PACKET_HAS_PARENT_INFO, sizeof(AvatarDataPacket::ParentInfo)) { auto startSection = destinationBuffer; auto parentInfo = reinterpret_cast(destinationBuffer); QByteArray referentialAsBytes = parentID.toRfc4122(); @@ -514,7 +539,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - if (hasAvatarLocalPosition) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_LOCAL_POSITION, sizeof(getLocalPosition()) ) { auto startSection = destinationBuffer; const auto localPosition = getLocalPosition(); AVATAR_MEMCPY(localPosition); @@ -525,11 +550,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // If it is connected, pack up the data - if (hasFaceTrackerInfo) { + IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, blendshapeCoefficients.size() * (int)sizeof(float)) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // note: we don't use the blink and average loudness, we just use the numBlendShapes and // compute the procedural info on the client side. faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; @@ -553,26 +578,26 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent QReadLocker readLock(&_jointDataLock); jointData = _jointData; } + const int numJoints = jointData.size(); + const int jointBitVectorSize = calcBitVectorSize(numJoints); // If it is connected, pack up the data - if (hasJointData) { + if (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA) { auto startSection = destinationBuffer; // joint rotation data - int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; unsigned char* validityPosition = destinationBuffer; unsigned char validity = 0; int validityBit = 0; - int numValidityBytes = calcBitVectorSize(numJoints); #ifdef WANT_DEBUG int rotationSentCount = 0; unsigned char* beforeRotations = destinationBuffer; #endif - destinationBuffer += numValidityBytes; // Move pointer past the validity bytes + destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes // sentJointDataOut and lastSentJointData might be the same vector if (sentJointDataOut) { @@ -625,7 +650,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent unsigned char* beforeTranslations = destinationBuffer; #endif - destinationBuffer += numValidityBytes; // Move pointer past the validity bytes + destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; @@ -718,6 +743,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (outboundDataRateOut) { outboundDataRateOut->farGrabJointRate.increment(numBytes); } + + includedFlags |= AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; } #ifdef WANT_DEBUG @@ -738,13 +765,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent outboundDataRateOut->jointDataRate.increment(numBytes); } + includedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; } - - if (hasJointDefaultPoseFlags) { + + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS, 1 + 2 * jointBitVectorSize) { auto startSection = destinationBuffer; // write numJoints - int numJoints = jointData.size(); *destinationBuffer++ = (uint8_t)numJoints; // write rotationIsDefaultPose bits @@ -763,6 +790,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } + memcpy(packetFlagsLocation, &includedFlags, sizeof(includedFlags)); + // Return dropped items. + itemFlags = packetStateFlags & ~includedFlags; + int avatarDataSize = destinationBuffer - startPosition; if (avatarDataSize > (int)byteArraySize) { @@ -771,6 +802,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } return avatarDataByteArray.left(avatarDataSize); + +#undef AVATAR_MEMCPY +#undef IF_AVATAR_SPACE } // NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 056e745370..057b351670 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -450,7 +450,7 @@ public: virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, - QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut = nullptr) const; + QVector* sentJointDataOut, int maxDataSize = 0, AvatarDataRate* outboundDataRateOut = nullptr) const; virtual void doneEncoding(bool cullSmallChanges); From ff0d938d3793e80d9e5c699115981ea96af453ba Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 17 Sep 2018 16:53:15 -0700 Subject: [PATCH 014/276] initial changes to stop the squatty potty from happening when users are sitting down in vr mode --- interface/resources/qml/AnimStats.qml | 2 +- interface/src/avatar/MyAvatar.cpp | 84 +++++++++++++++++++++--- interface/src/avatar/MyAvatar.h | 6 +- interface/src/avatar/MySkeletonModel.cpp | 2 +- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index 35ed3799a6..fd0a280dad 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -53,7 +53,7 @@ Item { ListView { width: firstCol.width height: root.animStateMachines.length * 15 - visible: root.animStateMchines.length > 0; + visible: root.animStateMachines.length > 0; model: root.animStateMachines delegate: StatText { text: { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c47cfdb383..b626fb6c2a 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3589,9 +3589,11 @@ glm::vec3 MyAvatar::computeCounterBalance() { // if the height is higher than default hips, clamp to default hips counterBalancedCg.y = tposeHips.y + 0.05f; } else if (counterBalancedCg.y < sitSquatThreshold) { - //do a height reset + // do a height reset setResetMode(true); _follow.activate(FollowHelper::Vertical); + // disable cg behaviour in this case. + _isInSittingState = true; } return counterBalancedCg; } @@ -3832,6 +3834,10 @@ bool MyAvatar::getIsInWalkingState() const { return _isInWalkingState; } +bool MyAvatar::getIsInSittingState() const { + return _isInSittingState; +} + float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } @@ -3852,6 +3858,10 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { _isInWalkingState = isWalking; } +void MyAvatar::setIsInSittingState(bool isSitting) { + _isInSittingState = isSitting; +} + void MyAvatar::setWalkSpeed(float value) { _walkSpeed.set(value); } @@ -4029,6 +4039,33 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; } +bool MyAvatar::FollowHelper::shouldActivateHorizontalSitting(MyAvatar& myAvatar) const { + + // get the current readings + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + 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)) { + // a step is detected + stepDetected = 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; + } + } + return stepDetected; +} + bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { // get the current readings @@ -4072,33 +4109,60 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons return stepDetected; } -bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { +bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { const float CYLINDER_TOP = 0.1f; const float CYLINDER_BOTTOM = -1.5f; + const float SITTING_BOTTOM = -0.02f; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); - return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + if (myAvatar.getIsInSittingState()) { + if (offset.y < SITTING_BOTTOM) { + // we recenter when sitting. + return true; + } else if (offset.y > CYLINDER_TOP) { + // if we recenter upwards then no longer in sitting state + myAvatar.setIsInSittingState(false); + return true; + } else { + return false; + } + } else { + return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + } } void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { - if (myAvatar.getHMDLeanRecenterEnabled() && - qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { + qCDebug(interfaceapp) << "in sitting state: " << myAvatar.getIsInSittingState(); + + if (myAvatar.getHMDLeanRecenterEnabled() && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } if (myAvatar.getCenterOfGravityModelEnabled()) { - if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { - activate(Horizontal); - if (myAvatar.getEnableStepResetRotation()) { - activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + if (!(myAvatar.getIsInSittingState())) { + if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { + activate(Horizontal); + if (myAvatar.getEnableStepResetRotation()) { + activate(Rotation); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + } + } + } else { + // you are in the sitting state with cg model enabled + if (!isActive(Horizontal) && (shouldActivateHorizontalSitting(myAvatar) || hasDriveInput)) { + activate(Horizontal); + if (myAvatar.getEnableStepResetRotation()) { + activate(Rotation); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); + } } } } else { + // center of gravity model is not enabled if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Horizontal); if (myAvatar.getEnableStepResetRotation()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 139f1f6ea2..a7fdf964d0 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1094,6 +1094,8 @@ public: void setIsInWalkingState(bool isWalking); bool getIsInWalkingState() const; + void setIsInSittingState(bool isSitting); + bool getIsInSittingState() const; void setWalkSpeed(float value); float getWalkSpeed() const; void setWalkBackwardSpeed(float value); @@ -1708,9 +1710,10 @@ private: float getMaxTimeRemaining() const; void decrementTimeRemaining(float dt); bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; - bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; + bool shouldActivateVertical(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; + bool shouldActivateHorizontalSitting(MyAvatar& myAvatar) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); bool getForceActivateRotation() const; @@ -1800,6 +1803,7 @@ private: ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; + bool _isInSittingState{ false }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 3084542472..42ec582c47 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 && !(myAvatar->getIsInWalkingState())) { + if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState())) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); } else { From 14fb7e1d44f8e52fbc91f8810f48d78b776b3005 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 17 Sep 2018 17:53:08 -0700 Subject: [PATCH 015/276] removed redundant code --- interface/src/avatar/MyAvatar.cpp | 52 ++++++------------------------- 1 file changed, 9 insertions(+), 43 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b626fb6c2a..d4168616c3 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4039,33 +4039,6 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; } -bool MyAvatar::FollowHelper::shouldActivateHorizontalSitting(MyAvatar& myAvatar) const { - - // get the current readings - controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); - 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)) { - // a step is detected - stepDetected = 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; - } - } - return stepDetected; -} - bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { // get the current readings @@ -4078,6 +4051,10 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons if (myAvatar.getIsInWalkingState()) { stepDetected = true; + } else if (myAvatar.getIsInSittingState()) { + if (!withinBaseOfSupport(currentHeadPose)) { + stepDetected = true; + } } else { if (!withinBaseOfSupport(currentHeadPose) && headAngularVelocityBelowThreshold(currentHeadPose) && @@ -4143,22 +4120,11 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } if (myAvatar.getCenterOfGravityModelEnabled()) { - if (!(myAvatar.getIsInSittingState())) { - if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { - activate(Horizontal); - if (myAvatar.getEnableStepResetRotation()) { - activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); - } - } - } else { - // you are in the sitting state with cg model enabled - if (!isActive(Horizontal) && (shouldActivateHorizontalSitting(myAvatar) || hasDriveInput)) { - activate(Horizontal); - if (myAvatar.getEnableStepResetRotation()) { - activate(Rotation); - myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); - } + if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { + activate(Horizontal); + if (myAvatar.getEnableStepResetRotation()) { + activate(Rotation); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } } } else { From 0c7dee730c60452f262ef507aa89a3d6609d6011 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 18 Sep 2018 10:25:59 -0700 Subject: [PATCH 016/276] Pack partial avatar data in bulk packets using space available Keep a single packet in-hand and send when full rather than a packet list. --- .../src/avatars/AvatarMixerSlave.cpp | 60 +++++++++++----- libraries/avatars/src/AvatarData.cpp | 72 ++++++++++--------- 2 files changed, 79 insertions(+), 53 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 20d85a3d4d..c6d8c7f495 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -279,10 +279,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int minimumBytesPerAvatar = PALIsOpen ? AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID + sizeof(AvatarDataPacket::AvatarGlobalPosition) + sizeof(AvatarDataPacket::AudioLoudness) : 0; - // setup a PacketList for the avatarPackets - auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); - static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; - // 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); @@ -401,6 +397,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); + auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); + const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); + int avatarSpaceAvailable = avatarPacketCapacity; + //avatarSpaceAvailable = 100; + int numPacketsSent = 0; const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); for (const auto& sortedAvatar : sortedAvatarVector) { @@ -460,17 +461,40 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool distanceAdjust = true; glm::vec3 viewerPosition = myPosition; - AvatarDataPacket::HasFlags hasFlagsOut = 0; // the result of the toByteArray + AvatarDataPacket::HasFlags includeFlags = 0; // the result of the toByteArray bool dropFaceTracking = false; - auto startSerialize = chrono::high_resolution_clock::now(); - QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, - &lastSentJointsForOther, maxAvatarDataBytes); - auto endSerialize = chrono::high_resolution_clock::now(); - _stats.toByteArrayElapsedTime += - (quint64) chrono::duration_cast(endSerialize - startSerialize).count(); + do { + auto startSerialize = chrono::high_resolution_clock::now(); + QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + includeFlags, dropFaceTracking, distanceAdjust, viewerPosition, + &lastSentJointsForOther, avatarSpaceAvailable); + auto endSerialize = chrono::high_resolution_clock::now(); + _stats.toByteArrayElapsedTime += + (quint64)chrono::duration_cast(endSerialize - startSerialize).count(); + avatarPacket->write(bytes); + avatarSpaceAvailable -= bytes.size(); + if (includeFlags != 0) { + // Weren't able to fit everything. + nodeList->sendPacket(std::move(avatarPacket), *destinationNode); + ++numPacketsSent; + avatarPacket = NLPacket::create(PacketType::BulkAvatarData); + avatarSpaceAvailable = avatarPacketCapacity; + } + } while (includeFlags != 0); + if (detail != AvatarData::NoData) { + _stats.numOthersIncluded++; + + // increment the number of avatars sent to this reciever + nodeData->incrementNumAvatarsSentLastFrame(); + + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + otherNodeData->getLastReceivedSequenceNumber()); + nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); + } +#if 0 if (bytes.size() > maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; @@ -517,6 +541,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // TODO? this avatar is not included now, and will probably not be included next frame. // It would be nice if we could tweak its future sort priority to put it at the back of the list. } +#endif auto endAvatarDataPacking = chrono::high_resolution_clock::now(); _stats.avatarDataPackingElapsedTime += @@ -534,15 +559,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) quint64 startPacketSending = usecTimestampNow(); - // close the current packet so that we're always sending something - avatarPacketList->closeCurrentPacket(true); + if (avatarPacket->getPayloadSize() != 0) { + nodeList->sendPacket(std::move(avatarPacket), *destinationNode); + ++numPacketsSent; + } - _stats.numPacketsSent += (int)avatarPacketList->getNumPackets(); + _stats.numPacketsSent += numPacketsSent; _stats.numBytesSent += numAvatarDataBytes; - // send the avatar data PacketList - nodeList->sendPacketList(std::move(avatarPacketList), *destinationNode); - // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index fdd5317bce..6e5dc3fc6a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -65,7 +65,7 @@ size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients } size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) { - const size_t validityBitsSize = (size_t)std::ceil(numJoints / (float)BITS_IN_BYTE); + const size_t validityBitsSize = (size_t) calcBitVectorSize(numJoints); size_t totalSize = sizeof(uint8_t); // numJoints @@ -270,10 +270,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent auto parentID = getParentID(); - bool hasJointData = false; - bool hasJointDefaultPoseFlags = false; - bool hasGrabJoints = false; - glm::mat4 leftFarGrabMatrix; glm::mat4 rightFarGrabMatrix; glm::mat4 mouseFarGrabMatrix; @@ -289,6 +285,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool hasLookAtPosition = false; bool hasAudioLoudness = false; bool hasSensorToWorldMatrix = false; + bool hasJointData = false; + bool hasJointDefaultPoseFlags = false; + bool hasGrabJoints = false; bool hasAdditionalFlags = false; // local position, and parent info only apply to avatars that are parented. The local position @@ -354,13 +353,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); + itemFlags = packetStateFlags; } else { packetStateFlags = itemFlags; } - - const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + - (packetStateFlags & AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + + const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID + + AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) + (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA ? AvatarDataPacket::maxJointDataSize(_jointData.size(), true) : 0) + (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); @@ -371,25 +370,27 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent QByteArray avatarDataByteArray((int)byteArraySize, 0); unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; + const unsigned char * packetEnd = destinationBuffer + maxDataSize; + + AvatarDataPacket::HasFlags includedFlags = 0; + + // Packets always have UUID. + memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); + destinationBuffer += NUM_BYTES_RFC4122_UUID; unsigned char * packetFlagsLocation = destinationBuffer; destinationBuffer += sizeof(packetStateFlags); if (itemFlags == 0) { - itemFlags = packetStateFlags; } - AvatarDataPacket::HasFlags includedFlags = 0; - - const unsigned char * packetEnd = destinationBuffer + maxDataSize; - #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); // If we want an item and there's sufficient space: -#define IF_AVATAR_SPACE(flag, space) \ - if ((itemFlags & AvatarDataPacket::flag) && (packetEnd - destinationBuffer) >= (space) \ +#define IF_AVATAR_SPACE(flag, space) \ + if ((itemFlags & AvatarDataPacket::flag) && (int)(packetEnd - destinationBuffer) >= (space) \ && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { @@ -414,7 +415,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } } - IF_AVATAR_SPACE(PACKET_HAS_AVATAR_ORIENTATION, 6) { + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_ORIENTATION, sizeof(AvatarDataPacket::SixByteQuat)) { auto startSection = destinationBuffer; auto localOrientation = getOrientationOutbound(); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); @@ -552,7 +553,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // If it is connected, pack up the data - IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, blendshapeCoefficients.size() * (int)sizeof(float)) { + IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, sizeof(AvatarDataPacket::FaceTrackerInfo) + blendshapeCoefficients.size() * sizeof(float)) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); // note: we don't use the blink and average loudness, we just use the numBlendShapes and @@ -574,15 +575,16 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } QVector jointData; - if (hasJointData || hasJointDefaultPoseFlags) { + if (packetStateFlags & (AvatarDataPacket::PACKET_HAS_JOINT_DATA | AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS)) { QReadLocker readLock(&_jointDataLock); jointData = _jointData; } const int numJoints = jointData.size(); const int jointBitVectorSize = calcBitVectorSize(numJoints); - // If it is connected, pack up the data - if (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA) { + // Check against full size or minimum size: count + two bit-vectors + two controllers + const int approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(numJoints, true) : 1 + 2 * jointBitVectorSize + 2 * (6 + 2); + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { auto startSection = destinationBuffer; // joint rotation data @@ -611,8 +613,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const JointData& last = lastSentJointData[i]; if (!data.rotationIsDefaultPose) { - // 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 + // 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); @@ -705,7 +707,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), TRANSLATION_COMPRESSION_RADIX); - if (hasGrabJoints) { + int numGrabJointBytes = 0; + IF_AVATAR_SPACE(PACKET_HAS_GRAB_JOINTS, sizeof (AvatarDataPacket::FarGrabJoints)) { // 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); @@ -738,13 +741,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; destinationBuffer += sizeof(data->mouseFarGrabRotation); - int numBytes = destinationBuffer - startSection; - - if (outboundDataRateOut) { - outboundDataRateOut->farGrabJointRate.increment(numBytes); - } - - includedFlags |= AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; + int numGrabJointBytes = destinationBuffer - startSection; } #ifdef WANT_DEBUG @@ -760,12 +757,17 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } #endif - int numBytes = destinationBuffer - startSection; - if (outboundDataRateOut) { - outboundDataRateOut->jointDataRate.increment(numBytes); + if (destinationBuffer >= packetEnd) { + // Joint data too large - revert + destinationBuffer = startSection; + includedFlags &= ~(AvatarDataPacket::PACKET_HAS_GRAB_JOINTS | AvatarDataPacket::PACKET_HAS_JOINT_DATA); + } else { + int numBytes = destinationBuffer - startSection; + if (outboundDataRateOut) { + outboundDataRateOut->jointDataRate.increment(numBytes); + outboundDataRateOut->farGrabJointRate.increment(numGrabJointBytes); + } } - - includedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; } IF_AVATAR_SPACE(PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS, 1 + 2 * jointBitVectorSize) { From 96da9b7e54ddaec7e976527059e878d0e3e27b89 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 18 Sep 2018 10:31:19 -0700 Subject: [PATCH 017/276] cleaned up code for squatty fix --- interface/resources/qml/AnimStats.qml | 2 +- interface/src/avatar/MyAvatar.cpp | 6 +++--- interface/src/avatar/MyAvatar.h | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index fd0a280dad..35ed3799a6 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -53,7 +53,7 @@ Item { ListView { width: firstCol.width height: root.animStateMachines.length * 15 - visible: root.animStateMachines.length > 0; + visible: root.animStateMchines.length > 0; model: root.animStateMachines delegate: StatText { text: { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d4168616c3..8eceb19e09 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4074,6 +4074,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); if (!isActive(Horizontal) && + (!isActive(Vertical)) && (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + (DEFAULT_AVATAR_SPINE_STRETCH_LIMIT * anatomicalHeadToHipsDistance)))) { myAvatar.setResetMode(true); stepDetected = true; @@ -4112,9 +4113,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) { - qCDebug(interfaceapp) << "in sitting state: " << myAvatar.getIsInSittingState(); - - if (myAvatar.getHMDLeanRecenterEnabled() && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { + if (myAvatar.getHMDLeanRecenterEnabled() && + qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a7fdf964d0..6f3858c5bf 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1713,7 +1713,6 @@ private: bool shouldActivateVertical(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; - bool shouldActivateHorizontalSitting(MyAvatar& myAvatar) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); bool getForceActivateRotation() const; @@ -1803,7 +1802,7 @@ private: ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; - bool _isInSittingState{ false }; + bool _isInSittingState { false }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; From 42e248ef84ca8207ca92885f04768935e8457062 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 18 Sep 2018 17:37:44 -0700 Subject: [PATCH 018/276] maded more changes to support sitting state to do: add option for stuck pose reset --- interface/src/avatar/MyAvatar.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8eceb19e09..bf684ccd9a 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -453,6 +453,16 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); + // qCDebug(interfaceapp) << "sitting state is " << getIsInSittingState(); + // debug setting for sitting state if you start in the chair. + // if the head is close to the floor in sensor space + // and the up of the head is close to sensor up then try switching us to sitting state. + auto sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); + glm::vec3 upHead = transformVectorFast(sensorHeadPoseDebug.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); + float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); + if ((acosHead > 0.98f) && !getIsInSittingState() && (sensorHeadPoseDebug.getTranslation().y < -0.5f)) { + qCDebug(interfaceapp) << "we are going to sitting state because it looks like we should" << sensorHeadPoseDebug.getTranslation().y; + } // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) { @@ -3593,7 +3603,7 @@ glm::vec3 MyAvatar::computeCounterBalance() { setResetMode(true); _follow.activate(FollowHelper::Vertical); // disable cg behaviour in this case. - _isInSittingState = true; + setIsInSittingState(true); } return counterBalancedCg; } @@ -4049,6 +4059,12 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons bool stepDetected = false; float myScale = myAvatar.getAvatarScale(); + + // debug head hips angle + //glm::vec3 hipsPos = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); + //glm::vec3 headHipsBody = currentHeadPose.getTranslation() - hipsPos; + //qCDebug(interfaceapp) << "head in sensor space " << withinBaseOfSupport(currentHeadPose); + if (myAvatar.getIsInWalkingState()) { stepDetected = true; } else if (myAvatar.getIsInSittingState()) { @@ -4094,6 +4110,12 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); + auto sensorHeadPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); + glm::vec3 headWorldSpace = myAvatar.getHead()->getPosition(); + glm::mat4 worldToSensorMatrix = glm::inverse(myAvatar.getSensorToWorldMatrix()); + glm::vec3 headSensorSpace = transformVectorFast(myAvatar.getSensorToWorldMatrix(), headWorldSpace); + //qCDebug(interfaceapp) << "sensor space position " << extractTranslation(currentBodyMatrix) << " head position sensor " << sensorHeadPose.getTranslation(); + if (myAvatar.getIsInSittingState()) { if (offset.y < SITTING_BOTTOM) { // we recenter when sitting. From 5d91396e91320b354fc24b5e1be30cd2ade97755 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 18 Sep 2018 17:49:23 -0700 Subject: [PATCH 019/276] Fixes for client & other clean-up Handle grab-joints better; fix packet size calc; remove dead code; other improvements. --- .../src/avatars/AvatarMixerSlave.cpp | 60 ++------------ libraries/avatars/src/AvatarData.cpp | 82 +++++++++++-------- 2 files changed, 52 insertions(+), 90 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index c6d8c7f495..2aa8de6cf4 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -261,7 +261,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // max number of avatarBytes per frame int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); - // keep track of the number of other avatars held back in this frame int numAvatarsHeldBack = 0; @@ -457,23 +456,22 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool includeThisAvatar = true; QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); - lastSentJointsForOther.resize(otherAvatar->getJointCount()); - - bool distanceAdjust = true; - glm::vec3 viewerPosition = myPosition; + const bool distanceAdjust = true; + const bool dropFaceTracking = false; AvatarDataPacket::HasFlags includeFlags = 0; // the result of the toByteArray - bool dropFaceTracking = false; do { auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - includeFlags, dropFaceTracking, distanceAdjust, viewerPosition, + includeFlags, dropFaceTracking, distanceAdjust, myPosition, &lastSentJointsForOther, avatarSpaceAvailable); auto endSerialize = chrono::high_resolution_clock::now(); _stats.toByteArrayElapsedTime += (quint64)chrono::duration_cast(endSerialize - startSerialize).count(); + avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); + numAvatarDataBytes += bytes.size(); if (includeFlags != 0) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); @@ -494,54 +492,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) otherNodeData->getLastReceivedSequenceNumber()); nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); } -#if 0 - if (bytes.size() > maxAvatarDataBytes) { - qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() - << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; - - dropFaceTracking = true; // first try dropping the facial data - bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther, maxAvatarDataBytes); - - if (bytes.size() > maxAvatarDataBytes) { - qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() - << "without facial data resulted in very large buffer of" << bytes.size() - << "bytes - reducing to MinimumData"; - bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther, maxAvatarDataBytes); - - if (bytes.size() > maxAvatarDataBytes, maxAvatarDataBytes) { - qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() - << "MinimumData resulted in very large buffer of" << bytes.size() - << "bytes - refusing to send avatar"; - includeThisAvatar = false; - } - } - } - - 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++; - - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); - - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); - nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); - } - } else { - // TODO? this avatar is not included now, and will probably not be included next frame. - // It would be nice if we could tweak its future sort priority to put it at the back of the list. - } -#endif auto endAvatarDataPacking = chrono::high_resolution_clock::now(); _stats.avatarDataPackingElapsedTime += diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6e5dc3fc6a..2b46eb9cdd 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -65,7 +65,7 @@ size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients } size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) { - const size_t validityBitsSize = (size_t) calcBitVectorSize(numJoints); + const size_t validityBitsSize = calcBitVectorSize((int)numJoints); size_t totalSize = sizeof(uint8_t); // numJoints @@ -74,9 +74,9 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) totalSize += validityBitsSize; // Translations mask totalSize += numJoints * sizeof(SixByteTrans); // Translations - size_t NUM_FAUX_JOINT = 2; - size_t num_grab_joints = (hasGrabJoints ? 2 : 0); - totalSize += (NUM_FAUX_JOINT + num_grab_joints) * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints + static const size_t NUM_FAUX_JOINT = 2; + totalSize += NUM_FAUX_JOINT * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints + totalSize += hasGrabJoints ? sizeof(FarGrabJoints) : 0; return totalSize; } @@ -222,12 +222,14 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio // we want to track outbound data in this case... QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) { - AvatarDataPacket::HasFlags hasFlagsOut; + AvatarDataPacket::HasFlags hasFlagsOut = 0; auto lastSentTime = _lastToByteArray; _lastToByteArray = usecTimestampNow(); - return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), + auto avatarByteArray = AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), hasFlagsOut, dropFaceTracking, false, glm::vec3(0), nullptr, 0, &_outboundDataRate); + // Strip UUID + return avatarByteArray.right(avatarByteArray.size() - NUM_BYTES_RFC4122_UUID); } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, @@ -245,7 +247,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { AvatarDataPacket::HasFlags packetStateFlags = 0; - QByteArray avatarDataByteArray(reinterpret_cast(&packetStateFlags), sizeof(packetStateFlags)); + + QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof(packetStateFlags)); + avatarDataByteArray.append((char*) &packetStateFlags, sizeof packetStateFlags); return avatarDataByteArray; } @@ -287,7 +291,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool hasSensorToWorldMatrix = false; bool hasJointData = false; bool hasJointDefaultPoseFlags = false; - bool hasGrabJoints = false; bool hasAdditionalFlags = false; // local position, and parent info only apply to avatars that are parented. The local position @@ -315,26 +318,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); - hasJointData = sendAll || !sendMinimum; + hasJointData = !sendMinimum; hasJointDefaultPoseFlags = hasJointData; - if (hasJointData) { - bool leftValid; - leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); - if (!leftValid) { - leftFarGrabMatrix = glm::mat4(); - } - bool rightValid; - rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); - if (!rightValid) { - rightFarGrabMatrix = glm::mat4(); - } - bool mouseValid; - mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); - if (!mouseValid) { - mouseFarGrabMatrix = glm::mat4(); - } - hasGrabJoints = (leftValid || rightValid || mouseValid); - } } packetStateFlags = @@ -351,17 +336,45 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0) | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) - | (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); + | (hasJointData ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); itemFlags = packetStateFlags; } else { packetStateFlags = itemFlags; + if (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA) { + // Force all joints upon continuation, as deltas aren't valid. + sendAll = true; + } + if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + packetStateFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; + } + } + + if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + bool leftValid; + leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); + if (!leftValid) { + leftFarGrabMatrix = glm::mat4(); + } + bool rightValid; + rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid); + if (!rightValid) { + rightFarGrabMatrix = glm::mat4(); + } + bool mouseValid; + mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid); + if (!mouseValid) { + mouseFarGrabMatrix = glm::mat4(); + } + if (!(leftValid || rightValid || mouseValid)) { + packetStateFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; + } } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID + AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) + - (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA ? AvatarDataPacket::maxJointDataSize(_jointData.size(), true) : 0) + - (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); + AvatarDataPacket::maxJointDataSize(_jointData.size(), true) + + AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()); if (maxDataSize == 0) { maxDataSize = (int)byteArraySize; @@ -381,16 +394,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent unsigned char * packetFlagsLocation = destinationBuffer; destinationBuffer += sizeof(packetStateFlags); - if (itemFlags == 0) { - } - #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); // If we want an item and there's sufficient space: #define IF_AVATAR_SPACE(flag, space) \ - if ((itemFlags & AvatarDataPacket::flag) && (int)(packetEnd - destinationBuffer) >= (space) \ + if ((packetStateFlags & AvatarDataPacket::flag) && (int)(packetEnd - destinationBuffer) >= (space) \ && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { @@ -583,7 +593,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const int jointBitVectorSize = calcBitVectorSize(numJoints); // Check against full size or minimum size: count + two bit-vectors + two controllers - const int approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(numJoints, true) : 1 + 2 * jointBitVectorSize + 2 * (6 + 2); + const int approxJointSpace = sendAll ? (int)AvatarDataPacket::maxJointDataSize(numJoints, true) : + 1 + 2 * jointBitVectorSize + 2 * (sizeof(AvatarDataPacket::SixByteQuat) + sizeof(AvatarDataPacket::SixByteTrans)); + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { auto startSection = destinationBuffer; From c88a713ef86e4dacf4d46f5a845fef0acc769cad Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 19 Sep 2018 12:05:06 -0700 Subject: [PATCH 020/276] Use Local ID for last time & sequence # Also try to fix size_t warnings on gcc --- assignment-client/src/avatars/AvatarMixer.cpp | 6 +++--- .../src/avatars/AvatarMixerClientData.cpp | 14 +++++++------- .../src/avatars/AvatarMixerClientData.h | 18 +++++++++--------- .../src/avatars/AvatarMixerSlave.cpp | 17 ++++++++--------- libraries/avatars/src/AvatarData.cpp | 8 ++++---- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 561afee296..ebd86b749b 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -541,7 +541,7 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointersetLastBroadcastTime(node->getUUID(), 0); + nodeData->setLastBroadcastTime(node->getLocalID(), 0); nodeData->resetSentTraitData(node->getLocalID()); } ); @@ -637,7 +637,7 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer // Reset the lastBroadcastTime for the ignored avatar to 0 // so the AvatarMixer knows it'll have to send identity data about the ignored avatar // to the ignorer if the ignorer unignores. - nodeData->setLastBroadcastTime(ignoredUUID, 0); + nodeData->setLastBroadcastTime(ignoredNode->getLocalID(), 0); nodeData->resetSentTraitData(ignoredNode->getLocalID()); } @@ -647,7 +647,7 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer // to the ignored if the ignorer unignores. AvatarMixerClientData* ignoredNodeData = reinterpret_cast(ignoredNode->getLinkedData()); if (ignoredNodeData) { - ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0); + ignoredNodeData->setLastBroadcastTime(senderNode->getLocalID(), 0); ignoredNodeData->resetSentTraitData(senderNode->getLocalID()); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 6c01e6e02b..2226a4c7a6 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -200,7 +200,7 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa } } -uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const { +uint64_t AvatarMixerClientData::getLastBroadcastTime(NLPacket::LocalID nodeUUID) const { // return the matching PacketSequenceNumber, or the default if we don't have it auto nodeMatch = _lastBroadcastTimes.find(nodeUUID); if (nodeMatch != _lastBroadcastTimes.end()) { @@ -209,9 +209,9 @@ uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) cons return 0; } -uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const { +uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const { // return the matching PacketSequenceNumber, or the default if we don't have it - auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID); + auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeID); if (nodeMatch != _lastBroadcastSequenceNumbers.end()) { return nodeMatch->second; } @@ -232,7 +232,7 @@ void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) { } else { killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble); } - setLastBroadcastTime(other->getUUID(), 0); + setLastBroadcastTime(other->getLocalID(), 0); resetSentTraitData(other->getLocalID()); @@ -311,9 +311,9 @@ AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherA } } -void AvatarMixerClientData::cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID) { - removeLastBroadcastSequenceNumber(nodeUUID); - removeLastBroadcastTime(nodeUUID); +void AvatarMixerClientData::cleanupKilledNode(const QUuid&, Node::LocalID nodeLocalID) { + removeLastBroadcastSequenceNumber(nodeLocalID); + removeLastBroadcastTime(nodeLocalID); _lastSentTraitsTimestamps.erase(nodeLocalID); _sentTraitVersions.erase(nodeLocalID); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index d38a90ef1f..ccb0f4001d 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -49,14 +49,14 @@ public: const AvatarData* getConstAvatarData() const { return _avatar.get(); } AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } - uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const; - void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber) - { _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; } - Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); } + uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const; + void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber) + { _lastBroadcastSequenceNumbers[nodeID] = sequenceNumber; } + Q_INVOKABLE void removeLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) { _lastBroadcastSequenceNumbers.erase(nodeID); } - uint64_t getLastBroadcastTime(const QUuid& nodeUUID) const; - void setLastBroadcastTime(const QUuid& nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; } - Q_INVOKABLE void removeLastBroadcastTime(const QUuid& nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); } + uint64_t getLastBroadcastTime(NLPacket::LocalID nodeUUID) const; + void setLastBroadcastTime(NLPacket::LocalID nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; } + Q_INVOKABLE void removeLastBroadcastTime(NLPacket::LocalID nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); } Q_INVOKABLE void cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID); @@ -147,8 +147,8 @@ private: AvatarSharedPointer _avatar { new AvatarData() }; uint16_t _lastReceivedSequenceNumber { 0 }; - std::unordered_map _lastBroadcastSequenceNumbers; - std::unordered_map _lastBroadcastTimes; + std::unordered_map _lastBroadcastSequenceNumbers; + std::unordered_map _lastBroadcastTimes; // this is a map of the last time we encoded an "other" avatar for // sending to "this" node diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 2aa8de6cf4..ef10cb730a 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -359,7 +359,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } if (!shouldIgnore) { - AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); + AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getLocalID()); AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber(); // FIXME - This code does appear to be working. But it seems brittle. @@ -435,11 +435,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. if (otherAvatar->hasProcessedFirstIdentity() - && nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) { + && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { identityBytesSent += sendIdentityPacket(otherNodeData, node); // remember the last time we sent identity details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow()); + nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); } // Typically all out-of-view avatars but such avatars' priorities will rise with time: @@ -453,7 +453,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->incrementAvatarInView(); } - bool includeThisAvatar = true; QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); const bool distanceAdjust = true; @@ -484,11 +483,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; - // increment the number of avatars sent to this reciever + // increment the number of avatars sent to this receiver nodeData->incrementNumAvatarsSentLastFrame(); // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), otherNodeData->getLastReceivedSequenceNumber()); nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); } @@ -582,11 +581,11 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); - auto lastBroadcastTime = nodeData->getLastBroadcastTime(agentNode->getUUID()); + auto lastBroadcastTime = nodeData->getLastBroadcastTime(agentNode->getLocalID()); if (lastBroadcastTime <= agentNodeData->getIdentityChangeTimestamp() || (start - lastBroadcastTime) >= REBROADCAST_IDENTITY_TO_DOWNSTREAM_EVERY_US) { sendReplicatedIdentityPacket(*agentNode, agentNodeData, *node); - nodeData->setLastBroadcastTime(agentNode->getUUID(), start); + nodeData->setLastBroadcastTime(agentNode->getLocalID(), start); } // figure out how large our avatar byte array can be to fit in the packet list @@ -620,7 +619,7 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin nodeData->incrementNumAvatarsSentLastFrame(); // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(agentNode->getUUID(), + nodeData->setLastBroadcastSequenceNumber(agentNode->getLocalID(), agentNodeData->getLastReceivedSequenceNumber()); // increment the number of avatars sent to this reciever diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2b46eb9cdd..5c50805e3b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -400,7 +400,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // If we want an item and there's sufficient space: #define IF_AVATAR_SPACE(flag, space) \ - if ((packetStateFlags & AvatarDataPacket::flag) && (int)(packetEnd - destinationBuffer) >= (space) \ + if ((packetStateFlags & AvatarDataPacket::flag) && (size_t)(packetEnd - destinationBuffer) >= (size_t)(space) \ && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { @@ -563,7 +563,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); // If it is connected, pack up the data - IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, sizeof(AvatarDataPacket::FaceTrackerInfo) + blendshapeCoefficients.size() * sizeof(float)) { + IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, sizeof(AvatarDataPacket::FaceTrackerInfo) + (size_t)blendshapeCoefficients.size() * sizeof(float)) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); // note: we don't use the blink and average loudness, we just use the numBlendShapes and @@ -593,7 +593,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const int jointBitVectorSize = calcBitVectorSize(numJoints); // Check against full size or minimum size: count + two bit-vectors + two controllers - const int approxJointSpace = sendAll ? (int)AvatarDataPacket::maxJointDataSize(numJoints, true) : + const size_t approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(numJoints, true) : 1 + 2 * jointBitVectorSize + 2 * (sizeof(AvatarDataPacket::SixByteQuat) + sizeof(AvatarDataPacket::SixByteTrans)); IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { @@ -753,7 +753,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent data->mouseFarGrabRotation[3] = mouseFarGrabRotation.z; destinationBuffer += sizeof(data->mouseFarGrabRotation); - int numGrabJointBytes = destinationBuffer - startSection; + numGrabJointBytes = destinationBuffer - startSection; } #ifdef WANT_DEBUG From 04c47943ba61cf73dd34b2f4a266bc384d2ed7ef Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 19 Sep 2018 13:45:08 -0700 Subject: [PATCH 021/276] Handle small packet space correctly --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 2 ++ libraries/avatars/src/AvatarData.h | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index ef10cb730a..ee7dfd85f3 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -471,7 +471,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); numAvatarDataBytes += bytes.size(); - if (includeFlags != 0) { + if (includeFlags != 0 || avatarSpaceAvailable < AvatarDataPacket::MIN_BULK_PACKET_SIZE) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); ++numPacketsSent; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 5c50805e3b..20456012a3 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -243,10 +243,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool sendPALMinimum = (dataDetail == PALMinimum); lazyInitHeadData(); + ASSERT(maxDataSize == 0 || maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE); // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { AvatarDataPacket::HasFlags packetStateFlags = 0; + itemFlags = packetStateFlags; QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof(packetStateFlags)); avatarDataByteArray.append((char*) &packetStateFlags, sizeof packetStateFlags); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 057b351670..df7129b7a5 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -296,6 +296,8 @@ namespace AvatarDataPacket { } PACKED_END; const size_t FAR_GRAB_JOINTS_SIZE = 84; static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); + + const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation From f1b7097edb69ce6f0801538470f020919a481d89 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 19 Sep 2018 15:32:56 -0700 Subject: [PATCH 022/276] Priority experiment --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 2 +- libraries/shared/src/PrioritySortUtil.h | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index ee7dfd85f3..15330ab5c3 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -471,7 +471,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); numAvatarDataBytes += bytes.size(); - if (includeFlags != 0 || avatarSpaceAvailable < AvatarDataPacket::MIN_BULK_PACKET_SIZE) { + if (includeFlags != 0 || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); ++numPacketsSent; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 20456012a3..4007856a73 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -243,7 +243,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool sendPALMinimum = (dataDetail == PALMinimum); lazyInitHeadData(); - ASSERT(maxDataSize == 0 || maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE); + ASSERT(maxDataSize == 0 || (size_t)maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE); // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 3a40fead71..c3d0f352ce 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -23,7 +23,7 @@ const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY; namespace PrioritySortUtil { - constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; + constexpr float DEFAULT_ANGULAR_COEF { 2.5f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; constexpr float DEFAULT_AGE_COEF { 0.25f }; @@ -103,6 +103,9 @@ 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; + if (cosineAngle > 0.0f) { + cosineAngle = std::sqrt(cosineAngle); + } float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND); // the "age" term accumulates at the sum of all weights From 7bba4d4badaea550a67b3ffe4fac9416203a5e0c Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 19 Sep 2018 18:38:20 -0700 Subject: [PATCH 023/276] Change priorities in the correct place --- libraries/avatars/src/AvatarData.cpp | 2 +- libraries/shared/src/PrioritySortUtil.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4007856a73..73525f69a7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2879,7 +2879,7 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra value.extraInfo = object.property("extraInfo").toVariant().toMap(); } -float AvatarData::_avatarSortCoefficientSize { 8.0f }; +float AvatarData::_avatarSortCoefficientSize { 20.0f }; float AvatarData::_avatarSortCoefficientCenter { 4.0f }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index c3d0f352ce..8020bde09d 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -23,7 +23,7 @@ const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY; namespace PrioritySortUtil { - constexpr float DEFAULT_ANGULAR_COEF { 2.5f }; + constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; constexpr float DEFAULT_AGE_COEF { 0.25f }; From 328ed8d9766b2c2e1a418f00df8d0ceaca49a72c Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 20 Sep 2018 09:31:01 -0700 Subject: [PATCH 024/276] Remove unused variable --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 15330ab5c3..c8cc4d0c8a 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -250,9 +250,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // reset the number of sent avatars nodeData->resetNumAvatarsSentLastFrame(); - // keep a counter of the number of considered avatars - int numOtherAvatars = 0; - // keep track of outbound data rate specifically for avatar data int numAvatarDataBytes = 0; int identityBytesSent = 0; @@ -427,8 +424,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto startAvatarDataPacking = chrono::high_resolution_clock::now(); - ++numOtherAvatars; - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); From 0381630e73ccfdcb4afc6dd6833109409720d356 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Sep 2018 16:07:24 -0700 Subject: [PATCH 025/276] update some server-console dependencies after npm audit --- server-console/package-lock.json | 1907 ++++++++++++++---------------- server-console/package.json | 11 +- server-console/src/main.js | 13 +- 3 files changed, 906 insertions(+), 1025 deletions(-) diff --git a/server-console/package-lock.json b/server-console/package-lock.json index 4f12f2fa00..e27c3815f6 100644 --- a/server-console/package-lock.json +++ b/server-console/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@types/node": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.2.tgz", - "integrity": "sha512-A6Uv1anbsCvrRDtaUXS2xZ5tlzD+Kg7yMRlSLFDy3z0r7KlGXDzL14vELXIAgpk2aJbU3XeZZQRcEkLkowT92g==", + "version": "8.10.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.29.tgz", + "integrity": "sha512-zbteaWZ2mdduacm0byELwtRyhYE40aK+pAanQk415gr1eRuu67x7QGOLmn8jz5zI8LDK7d0WI/oT6r5Trz4rzQ==", "dev": true }, "abbrev": { @@ -21,10 +21,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "always-tail": { @@ -32,7 +32,14 @@ "resolved": "https://registry.npmjs.org/always-tail/-/always-tail-0.2.0.tgz", "integrity": "sha1-M5sa9E1QJQqgeg6H7Mw6JOxET/4=", "requires": { - "debug": "0.7.4" + "debug": "~0.7.2" + }, + "dependencies": { + "debug": { + "version": "0.7.4", + "resolved": "http://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + } } }, "ansi-regex": { @@ -47,70 +54,48 @@ "dev": true }, "asar": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/asar/-/asar-0.14.2.tgz", - "integrity": "sha512-eKo4ywQDq9dC/0Pu6UJsX4PxNi5ZlC4/NQ1JORUW4xkMRrEWpoLPpkngmQ6K7ZkioVjE2ZafLMmHPAQKMO0BdA==", + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/asar/-/asar-0.14.3.tgz", + "integrity": "sha512-+hNnVVDmYbv05We/a9knj/98w171+A94A9DNHj+3kXUr3ENTQoSEcfbJRvBBRHyOh4vukBYWujmHvvaMmQoQbg==", "dev": true, "requires": { - "chromium-pickle-js": "0.2.0", - "commander": "2.9.0", - "cuint": "0.2.2", - "glob": "6.0.4", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "mksnapshot": "0.3.1", + "chromium-pickle-js": "^0.2.0", + "commander": "^2.9.0", + "cuint": "^0.2.1", + "glob": "^6.0.4", + "minimatch": "^3.0.3", + "mkdirp": "^0.5.0", + "mksnapshot": "^0.3.0", "tmp": "0.0.28" }, "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, "glob": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", "dev": true, "requires": { - "inflight": "1.0.4", - "inherits": "2.0.1", - "minimatch": "3.0.4", - "once": "1.3.3", - "path-is-absolute": "1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.11" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } } } }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "asynckit": { "version": "0.4.0", @@ -129,9 +114,15 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base64-js": { "version": "1.2.0", @@ -139,14 +130,23 @@ "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", "dev": true }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, "binary": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", "dev": true, "requires": { - "buffers": "0.1.1", - "chainsaw": "0.1.0" + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" } }, "bl": { @@ -154,7 +154,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", "requires": { - "readable-stream": "2.0.6" + "readable-stream": "~2.0.5" }, "dependencies": { "isarray": { @@ -167,20 +167,20 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.1", - "isarray": "1.0.0", - "process-nextick-args": "1.0.6", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" } } } }, "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", + "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", "dev": true }, "boolbase": { @@ -188,14 +188,44 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { - "hoek": "4.2.1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, "buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -219,8 +249,8 @@ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { - "camelcase": "2.1.1", - "map-obj": "1.0.1" + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" } }, "caseless": { @@ -234,19 +264,30 @@ "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", "dev": true, "requires": { - "traverse": "0.3.9" + "traverse": ">=0.3.0 <0.4" } }, "cheerio": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", - "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", "requires": { - "css-select": "1.0.0", - "dom-serializer": "0.1.0", - "entities": "1.1.1", - "htmlparser2": "3.8.3", - "lodash": "3.10.1" + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" } }, "chromium-pickle-js": { @@ -260,9 +301,9 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "requires": { - "string-width": "1.0.1", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.0.0" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" } }, "co": { @@ -275,25 +316,22 @@ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz", "integrity": "sha1-9psZLT99keOC5Lcb3bd4eGGasMY=", "requires": { - "number-is-nan": "1.0.0" + "number-is-nan": "^1.0.0" } }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz", + "integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==", + "dev": true }, "compare-version": { "version": "0.1.2", @@ -308,34 +346,57 @@ "dev": true }, "concat-stream": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", - "integrity": "sha1-U/fUPFHF5D+ByP3QMyHGMb5o1hE=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { - "inherits": "2.0.1", - "readable-stream": "2.0.6", - "typedarray": "0.0.6" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" }, "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.1", - "isarray": "1.0.0", - "process-nextick-args": "1.0.6", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" } } } @@ -345,39 +406,21 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "requires": { - "hoek": "4.2.1" - } - } - } - }, "css-select": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", - "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "requires": { - "boolbase": "1.0.0", - "css-what": "1.0.0", - "domutils": "1.4.3", - "nth-check": "1.0.1" + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" } }, "css-what": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", - "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" }, "cuint": { "version": "0.2.2", @@ -386,24 +429,27 @@ "dev": true }, "dashdash": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.0.tgz", - "integrity": "sha1-parm/Z2OFWYk6w3ZJZ6xK6JFOFo=", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } + "assert-plus": "^1.0.0" } }, "debug": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.0.1.tgz", + "integrity": "sha512-K23FHJ/Mt404FSlp6gSZCevIbTMLX0j3fmHhUEhQ3Wq0FMODW3+cUSoLdy1Gx4polAf4t/lphhmHH35BB8cLYw==", + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } }, "decamelize": { "version": "1.2.0", @@ -416,19 +462,19 @@ "integrity": "sha1-rjvLfjTGWHmt/nfhnDD4ZgK0vbA=", "dev": true, "requires": { - "binary": "0.3.0", - "graceful-fs": "4.1.3", - "mkpath": "0.1.0", - "nopt": "3.0.6", - "q": "1.5.1", - "readable-stream": "1.1.14", + "binary": "^0.3.0", + "graceful-fs": "^4.1.3", + "mkpath": "^0.1.0", + "nopt": "^3.0.1", + "q": "^1.1.2", + "readable-stream": "^1.1.8", "touch": "0.0.3" } }, "deep-extend": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz", - "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, "delayed-stream": { @@ -441,8 +487,8 @@ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" + "domelementtype": "~1.1.1", + "entities": "~1.1.1" }, "dependencies": { "domelementtype": { @@ -458,95 +504,109 @@ "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" }, "domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", "requires": { - "domelementtype": "1.3.0" + "domelementtype": "1" } }, "domutils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", - "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", "requires": { - "domelementtype": "1.3.0" + "dom-serializer": "0", + "domelementtype": "1" } }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "optional": true, "requires": { - "jsbn": "0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "electron": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/electron/-/electron-1.8.4.tgz", - "integrity": "sha512-2f1cx0G3riMFODXFftF5AHXy+oHfhpntZHTDN66Hxtl09gmEr42B3piNEod9MEmw72f75LX2JfeYceqq1PF8cA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-3.0.0.tgz", + "integrity": "sha512-QN9X5vYa4kzJKniwhXlJwioX9qw2fDehdqxN/00KCLz/qnOz/IHLAHGikFjRwfEF2xnkmHxf61F8wn2LePPXXQ==", "dev": true, "requires": { - "@types/node": "8.10.2", - "electron-download": "3.3.0", - "extract-zip": "1.5.0" + "@types/node": "^8.0.24", + "electron-download": "^4.1.0", + "extract-zip": "^1.0.3" } }, "electron-download": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-3.3.0.tgz", - "integrity": "sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", + "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", "dev": true, "requires": { - "debug": "2.6.9", - "fs-extra": "0.30.0", - "home-path": "1.0.5", - "minimist": "1.2.0", - "nugget": "2.0.1", - "path-exists": "2.1.0", - "rc": "1.1.6", - "semver": "5.5.0", - "sumchecker": "1.3.1" + "debug": "^3.0.0", + "env-paths": "^1.0.0", + "fs-extra": "^4.0.1", + "minimist": "^1.2.0", + "nugget": "^2.0.1", + "path-exists": "^3.0.0", + "rc": "^1.2.1", + "semver": "^5.4.1", + "sumchecker": "^2.0.2" }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "dev": true, "requires": { - "graceful-fs": "4.1.3", - "jsonfile": "2.2.3", - "klaw": "1.3.1", - "path-is-absolute": "1.0.0", - "rimraf": "2.6.2" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true, + "optional": true + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "sumchecker": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-1.3.1.tgz", - "integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0=", - "dev": true, - "requires": { - "debug": "2.6.9", - "es6-promise": "4.2.4" - } + "semver": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", + "dev": true } } }, @@ -556,17 +616,17 @@ "integrity": "sha1-DboCXtM9DkW/j0DG6b487i+YbCg=" }, "electron-osx-sign": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.8.tgz", - "integrity": "sha1-8Ln63e2eHlTsNfqJh3tcbDTHvEA=", + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.10.tgz", + "integrity": "sha1-vk87ibKnWh3F8eckkIGrKSnKOiY=", "dev": true, "requires": { - "bluebird": "3.5.1", - "compare-version": "0.1.2", - "debug": "2.6.9", - "isbinaryfile": "3.0.2", - "minimist": "1.2.0", - "plist": "2.1.0" + "bluebird": "^3.5.0", + "compare-version": "^0.1.2", + "debug": "^2.6.8", + "isbinaryfile": "^3.0.2", + "minimist": "^1.2.0", + "plist": "^2.1.0" }, "dependencies": { "debug": { @@ -577,89 +637,75 @@ "requires": { "ms": "2.0.0" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true } } }, "electron-packager": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-12.0.0.tgz", - "integrity": "sha1-uC0k14ovIUA7v9FmpbFWmJTVzQw=", + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-12.1.2.tgz", + "integrity": "sha512-7UiTNquZqhQm+L0Oqn7bR/7Ry/7zGO/PKwFpSNqHbWxydoN2aNahKyWjOPhcxHCAz+C1uu+tdyRe7wEN0BaJsA==", "dev": true, "requires": { - "asar": "0.14.2", - "debug": "3.1.0", - "electron-download": "4.1.0", - "electron-osx-sign": "0.4.8", - "extract-zip": "1.5.0", - "fs-extra": "5.0.0", - "galactus": "0.2.0", - "get-package-info": "1.0.0", - "nodeify": "1.0.1", - "parse-author": "2.0.0", - "pify": "3.0.0", - "plist": "2.1.0", - "rcedit": "1.0.0", - "resolve": "1.5.0", - "sanitize-filename": "1.6.1", - "semver": "5.5.0", - "yargs-parser": "9.0.2" + "asar": "^0.14.0", + "debug": "^3.0.0", + "electron-download": "^4.1.1", + "electron-osx-sign": "^0.4.1", + "extract-zip": "^1.0.3", + "fs-extra": "^5.0.0", + "galactus": "^0.2.1", + "get-package-info": "^1.0.0", + "nodeify": "^1.0.1", + "parse-author": "^2.0.0", + "pify": "^3.0.0", + "plist": "^2.0.0", + "rcedit": "^1.0.0", + "resolve": "^1.1.6", + "sanitize-filename": "^1.6.0", + "semver": "^5.3.0", + "yargs-parser": "^10.0.0" }, "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "electron-download": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.0.tgz", - "integrity": "sha1-v5MsdG8vh//MCdHdRy8v9rkYeEU=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.1.tgz", + "integrity": "sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg==", "dev": true, "requires": { - "debug": "2.6.9", - "env-paths": "1.0.0", - "fs-extra": "2.1.2", - "minimist": "1.2.0", - "nugget": "2.0.1", - "path-exists": "3.0.0", - "rc": "1.1.6", - "semver": "5.5.0", - "sumchecker": "2.0.2" + "debug": "^3.0.0", + "env-paths": "^1.0.0", + "fs-extra": "^4.0.1", + "minimist": "^1.2.0", + "nugget": "^2.0.1", + "path-exists": "^3.0.0", + "rc": "^1.2.1", + "semver": "^5.4.1", + "sumchecker": "^2.0.2" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, "fs-extra": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz", - "integrity": "sha1-BGxwFjzvmq1GsOSn+kZ/si1x3jU=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "dev": true, "requires": { - "graceful-fs": "4.1.3", - "jsonfile": "2.2.3" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } } } @@ -670,63 +716,35 @@ "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", "dev": true, "requires": { - "graceful-fs": "4.1.3", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" }, "dependencies": { - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true, - "requires": { - "graceful-fs": "4.1.11" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true, - "optional": true - } - } + "optional": true } } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, - "nugget": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", - "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", - "dev": true, - "requires": { - "debug": "2.6.9", - "minimist": "1.2.0", - "pretty-bytes": "1.0.4", - "progress-stream": "1.2.0", - "request": "2.85.0", - "single-line-log": "1.1.2", - "throttleit": "0.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -739,41 +757,29 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, - "rcedit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-1.0.0.tgz", - "integrity": "sha512-W7DNa34x/3OgWyDHsI172AG/Lr/lZ+PkavFkHj0QhhkBRcV9QTmRJE1tDKrWkx8XHPSBsmZkNv9OKue6pncLFQ==", - "dev": true + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true }, - "single-line-log": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", - "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", - "dev": true, - "requires": { - "string-width": "1.0.1" - } - }, - "throttleit": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", - "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "4.1.0" - } } } }, @@ -782,7 +788,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", "integrity": "sha1-6TUyWLqpEIll78QcsO+K3i88+wc=", "requires": { - "once": "1.3.3" + "once": "~1.3.0" } }, "entities": { @@ -802,58 +808,46 @@ "integrity": "sha1-5ntD8+gsluo6WE/+4Ln8MyXYAtk=", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, - "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", - "dev": true - }, "extend": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=" }, "extract-zip": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", - "integrity": "sha1-ksz22B73Cp+kwXRxFMzvbYaIpsQ=", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", + "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", "dev": true, "requires": { - "concat-stream": "1.5.0", - "debug": "0.7.4", - "mkdirp": "0.5.0", + "concat-stream": "1.6.2", + "debug": "2.6.9", + "mkdirp": "0.5.1", "yauzl": "2.4.1" }, "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", - "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "minimist": "0.0.8" + "ms": "2.0.0" } } } }, "extsprintf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", - "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -866,7 +860,7 @@ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", "dev": true, "requires": { - "pend": "1.2.0" + "pend": "~1.2.0" } }, "find-up": { @@ -875,8 +869,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" }, "dependencies": { "path-exists": { @@ -885,28 +879,28 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } } } }, "flora-colossus": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-0.0.2.tgz", - "integrity": "sha1-fRvimh8X+k8isb1hSC+Gw04HuQE=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-1.0.0.tgz", + "integrity": "sha1-VHKcNh7ezuAU3UQWeeGjfB13OkU=", "dev": true, "requires": { - "debug": "3.1.0", - "fs-extra": "4.0.3" + "debug": "^3.1.0", + "fs-extra": "^4.0.0" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "fs-extra": { @@ -915,9 +909,9 @@ "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "dev": true, "requires": { - "graceful-fs": "4.1.3", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "jsonfile": { @@ -926,7 +920,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" }, "dependencies": { "graceful-fs": { @@ -939,9 +933,9 @@ } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true } } @@ -956,9 +950,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "mime-types": "^2.1.12" }, "dependencies": { "combined-stream": { @@ -966,19 +960,37 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } } } }, "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", + "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", "requires": { - "graceful-fs": "4.1.3", - "jsonfile": "2.2.3", - "klaw": "1.3.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "optional": true + } + } + } } }, "fs.realpath": { @@ -988,23 +1000,23 @@ "dev": true }, "galactus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/galactus/-/galactus-0.2.0.tgz", - "integrity": "sha1-w9Y7pVAkZv5A6mfMaJCFs90kqPw=", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/galactus/-/galactus-0.2.1.tgz", + "integrity": "sha1-y+0tIKQMH1Z5o1kI4rlBVzPnjbk=", "dev": true, "requires": { - "debug": "3.1.0", - "flora-colossus": "0.0.2", - "fs-extra": "4.0.3" + "debug": "^3.1.0", + "flora-colossus": "^1.0.0", + "fs-extra": "^4.0.0" }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", + "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "fs-extra": { @@ -1013,9 +1025,9 @@ "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "dev": true, "requires": { - "graceful-fs": "4.1.3", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "jsonfile": { @@ -1024,7 +1036,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" }, "dependencies": { "graceful-fs": { @@ -1037,9 +1049,9 @@ } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true } } @@ -1050,10 +1062,10 @@ "integrity": "sha1-ZDJ5ZWPigRPNlHTbvQAFKYWkmZw=", "dev": true, "requires": { - "bluebird": "3.5.1", - "debug": "2.6.9", - "lodash.get": "4.4.2", - "read-pkg-up": "2.0.0" + "bluebird": "^3.1.1", + "debug": "^2.2.0", + "lodash.get": "^4.0.0", + "read-pkg-up": "^2.0.0" }, "dependencies": { "debug": { @@ -1071,7 +1083,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "load-json-file": { @@ -1080,25 +1092,19 @@ "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { - "graceful-fs": "4.1.3", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, "path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "pify": "2.3.0" + "pify": "^2.0.0" } }, "read-pkg": { @@ -1107,9 +1113,9 @@ "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.3.5", - "path-type": "2.0.0" + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" } }, "read-pkg-up": { @@ -1118,8 +1124,8 @@ "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" } }, "strip-bom": { @@ -1136,18 +1142,26 @@ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", "dev": true }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.4", - "inherits": "2.0.1", - "minimatch": "3.0.4", - "once": "1.3.3", - "path-is-absolute": "1.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "dependencies": { "balanced-match": { @@ -1162,7 +1176,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -1172,7 +1186,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } } } @@ -1182,12 +1196,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz", "integrity": "sha1-kgM84RETxB4mKNYf36QLwQ3AFVw=" }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, "growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -1199,36 +1207,14 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.3.0", + "har-schema": "^2.0.0" } }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" - } - }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" - }, - "home-path": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.5.tgz", - "integrity": "sha1-eIspgVsS1Tus9XVkhHbm+QQdEz8=", - "dev": true - }, "hosted-git-info": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz", @@ -1236,30 +1222,56 @@ "dev": true }, "htmlparser2": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.3.0", - "domutils": "1.5.1", - "entities": "1.0.0", - "readable-stream": "1.1.14" + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" }, "dependencies": { - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } } }, - "entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -1268,16 +1280,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.2.2", - "sshpk": "1.7.4" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "indent-string": { @@ -1286,7 +1291,7 @@ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" }, "dependencies": { "repeating": { @@ -1295,7 +1300,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "1.0.1" + "is-finite": "^1.0.0" } } } @@ -1306,8 +1311,8 @@ "integrity": "sha1-bLtFIevVHODsCpNr/XZX736bFyo=", "dev": true, "requires": { - "once": "1.3.3", - "wrappy": "1.0.1" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -1338,7 +1343,7 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-finite": { @@ -1347,7 +1352,7 @@ "integrity": "sha1-ZDhgPq6+J5OUj/SkJi7I2z1iWXs=", "dev": true, "requires": { - "number-is-nan": "1.0.0" + "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { @@ -1355,7 +1360,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "1.0.0" + "number-is-nan": "^1.0.0" } }, "is-promise": { @@ -1378,13 +1383,17 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true }, "isbinaryfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", - "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", - "dev": true + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", + "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "dev": true, + "requires": { + "buffer-alloc": "^1.2.0" + } }, "isexe": { "version": "2.0.0", @@ -1396,25 +1405,16 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, - "jodid25519": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", - "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", - "optional": true, - "requires": { - "jsbn": "0.1.0" - } - }, "jsbn": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz", - "integrity": "sha1-ZQmH2g3XT06/WhE3eiqi0nPpff0=", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "optional": true }, "json-schema": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz", - "integrity": "sha1-UDVPGfYDkXxpX3C4Wvp3w7DyNQY=" + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.3.1", @@ -1429,30 +1429,34 @@ "jsonfile": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.2.3.tgz", - "integrity": "sha1-4lK5mmr5AdPsQfMyWJyQUJp7xgU=" + "integrity": "sha1-4lK5mmr5AdPsQfMyWJyQUJp7xgU=", + "dev": true }, "jsprim": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz", - "integrity": "sha1-8gyQaskqvVjjt5rIvHCkiDJRLaE=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "requires": { - "extsprintf": "1.0.2", - "json-schema": "0.2.2", - "verror": "1.3.6" + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" } }, "klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.9" }, "dependencies": { "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true, "optional": true } } @@ -1462,7 +1466,7 @@ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "load-json-file": { @@ -1471,11 +1475,11 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "4.1.3", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, "locate-path": { @@ -1484,8 +1488,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" }, "dependencies": { "path-exists": { @@ -1496,10 +1500,35 @@ } } }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + "lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + }, + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" }, "lodash.get": { "version": "4.4.2", @@ -1507,14 +1536,44 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + }, + "lodash.merge": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", + "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==" + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" + }, "loud-rejection": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.3.0.tgz", "integrity": "sha1-8omjkvF9K6rPGU0KZzAEOUQzsRU=", "dev": true, "requires": { - "array-find-index": "1.0.1", - "signal-exit": "2.1.2" + "array-find-index": "^1.0.0", + "signal-exit": "^2.1.2" } }, "map-obj": { @@ -1529,29 +1588,38 @@ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "camelcase-keys": "2.1.0", - "decamelize": "1.2.0", - "loud-rejection": "1.3.0", - "map-obj": "1.0.1", - "minimist": "1.2.0", - "normalize-package-data": "2.3.5", - "object-assign": "4.0.1", - "read-pkg-up": "1.0.1", - "redent": "1.0.0", - "trim-newlines": "1.0.0" + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" } }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "requires": { - "mime-db": "1.33.0" + "mime-db": "~1.36.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -1589,220 +1657,21 @@ "requires": { "decompress-zip": "0.3.0", "fs-extra": "0.26.7", - "request": "2.83.0" + "request": "^2.79.0" }, "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", - "dev": true - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, - "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - } - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true - }, - "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", - "dev": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" - } - }, "fs-extra": { "version": "0.26.7", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", "dev": true, "requires": { - "graceful-fs": "4.1.3", - "jsonfile": "2.2.3", - "klaw": "1.3.1", - "path-is-absolute": "1.0.0", - "rimraf": "2.6.2" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" } - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "dev": true, - "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" - } - }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "dev": true, - "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.0", - "sntp": "2.1.0" - } - }, - "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", - "dev": true - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.2.2", - "sshpk": "1.7.4" - } - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", - "dev": true - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "dev": true, - "requires": { - "mime-db": "1.30.0" - } - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true - }, - "request": { - "version": "2.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", - "dev": true, - "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.1", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" - } - }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - }, - "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", - "dev": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", - "dev": true } } }, @@ -1817,10 +1686,10 @@ "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.2.1.tgz", "integrity": "sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg==", "requires": { - "growly": "1.3.0", - "semver": "5.5.0", - "shellwords": "0.1.1", - "which": "1.3.0" + "growly": "^1.3.0", + "semver": "^5.4.1", + "shellwords": "^0.1.1", + "which": "^1.3.0" }, "dependencies": { "semver": { @@ -1836,8 +1705,8 @@ "integrity": "sha1-ZKtpp7268DzhB7TwM1yHwLnpGx0=", "dev": true, "requires": { - "is-promise": "1.0.1", - "promise": "1.3.0" + "is-promise": "~1.0.0", + "promise": "~1.3.0" } }, "nopt": { @@ -1846,7 +1715,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.1.1" + "abbrev": "1" } }, "normalize-package-data": { @@ -1855,10 +1724,10 @@ "integrity": "sha1-jZJPFClg4Xd+f/4XBUNjHMfLAt8=", "dev": true, "requires": { - "hosted-git-info": "2.1.4", - "is-builtin-module": "1.0.0", - "semver": "5.1.0", - "validate-npm-package-license": "3.0.1" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "nth-check": { @@ -1866,7 +1735,7 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", "requires": { - "boolbase": "1.0.0" + "boolbase": "~1.0.0" } }, "nugget": { @@ -1875,12 +1744,12 @@ "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", "dev": true, "requires": { - "debug": "2.6.9", - "minimist": "1.2.0", - "pretty-bytes": "1.0.4", - "progress-stream": "1.2.0", - "request": "2.85.0", - "single-line-log": "1.1.2", + "debug": "^2.1.3", + "minimist": "^1.1.0", + "pretty-bytes": "^1.0.2", + "progress-stream": "^1.1.0", + "request": "^2.45.0", + "single-line-log": "^1.1.2", "throttleit": "0.0.2" }, "dependencies": { @@ -1907,9 +1776,9 @@ "integrity": "sha1-wCD1KcUoKt/dIz2R1LGBw9aG3Es=" }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.0.1", @@ -1928,7 +1797,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", "requires": { - "wrappy": "1.0.1" + "wrappy": "1" } }, "os-homedir": { @@ -1941,7 +1810,7 @@ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "requires": { - "lcid": "1.0.0" + "lcid": "^1.0.0" } }, "os-tmpdir": { @@ -1951,12 +1820,12 @@ "dev": true }, "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -1965,7 +1834,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.2.0" + "p-limit": "^1.1.0" } }, "p-try": { @@ -1980,7 +1849,7 @@ "integrity": "sha1-00YL8d3Q367tQtp1QkLmX7aEqB8=", "dev": true, "requires": { - "author-regex": "1.0.0" + "author-regex": "^1.0.0" } }, "parse-json": { @@ -1989,17 +1858,14 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.0" + "error-ex": "^1.2.0" } }, "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "2.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true }, "path-is-absolute": { "version": "1.0.0", @@ -2008,9 +1874,9 @@ "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-type": { @@ -2019,9 +1885,9 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "4.1.3", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "pend": { @@ -2053,7 +1919,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "plist": { @@ -2064,7 +1930,7 @@ "requires": { "base64-js": "1.2.0", "xmlbuilder": "8.2.2", - "xmldom": "0.1.27" + "xmldom": "0.1.x" } }, "pretty-bytes": { @@ -2073,8 +1939,8 @@ "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", "dev": true, "requires": { - "get-stdin": "4.0.1", - "meow": "3.7.0" + "get-stdin": "^4.0.1", + "meow": "^3.1.0" } }, "process-nextick-args": { @@ -2088,8 +1954,8 @@ "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", "dev": true, "requires": { - "speedometer": "0.1.4", - "through2": "0.2.3" + "speedometer": "~0.1.2", + "through2": "~0.2.3" } }, "promise": { @@ -2098,16 +1964,21 @@ "integrity": "sha1-5cyaTIJ45GZP/twBx9qEhCsEAXU=", "dev": true, "requires": { - "is-promise": "1.0.1" + "is-promise": "~1" } }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + }, "pump": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.1.tgz", "integrity": "sha1-8fFAn7m9EIW721drQ7hOxLXq3Bo=", "requires": { - "end-of-stream": "1.1.0", - "once": "1.3.3" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, "punycode": { @@ -2122,31 +1993,37 @@ "dev": true }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "rc": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz", - "integrity": "sha1-Q2UbdrauU7XIAvEVH6P8OwWZack=", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { - "deep-extend": "0.4.1", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "1.0.4" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" } }, + "rcedit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-1.1.0.tgz", + "integrity": "sha512-JkXJ0IrUcdupLoIx6gE4YcFaMVSGtu7kQf4NJoDJUnfBZGuATmJ2Yal2v55KTltp+WV8dGr7A0RtOzx6jmtM6Q==", + "dev": true + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.3.5", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { @@ -2155,19 +2032,20 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.1", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "redent": { @@ -2176,43 +2054,41 @@ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "requires": { - "indent-string": "2.1.0", - "strip-indent": "1.0.1" + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" } }, "request": { - "version": "2.85.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", - "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" }, "dependencies": { "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" } } }, @@ -2221,16 +2097,16 @@ "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-1.0.2.tgz", "integrity": "sha1-XUBvCBMJ32G0qKqDzVc032Pxi/U=", "requires": { - "throttleit": "1.0.0" + "throttleit": "^1.0.0" } }, "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { - "path-parse": "1.0.5" + "path-parse": "^1.0.5" } }, "rimraf": { @@ -2239,13 +2115,18 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sanitize-filename": { "version": "1.6.1", @@ -2253,7 +2134,7 @@ "integrity": "sha1-YS2hyWRz+gLczaktzVtKsWSmdyo=", "dev": true, "requires": { - "truncate-utf8-bytes": "1.0.2" + "truncate-utf8-bytes": "^1.0.0" } }, "semver": { @@ -2279,15 +2160,7 @@ "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", "dev": true, "requires": { - "string-width": "1.0.1" - } - }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "requires": { - "hoek": "4.2.1" + "string-width": "^1.0.1" } }, "spdx-correct": { @@ -2296,7 +2169,7 @@ "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", "dev": true, "requires": { - "spdx-license-ids": "1.2.1" + "spdx-license-ids": "^1.0.2" } }, "spdx-exceptions": { @@ -2311,8 +2184,8 @@ "integrity": "sha1-1SsUtelnB3FECvIlvLVjEirEUvY=", "dev": true, "requires": { - "spdx-exceptions": "1.0.4", - "spdx-license-ids": "1.2.1" + "spdx-exceptions": "^1.0.4", + "spdx-license-ids": "^1.0.0" } }, "spdx-license-ids": { @@ -2328,17 +2201,19 @@ "dev": true }, "sshpk": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.4.tgz", - "integrity": "sha1-rXtH3vymHIQV2WQkO2KwzmD7yjg=", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { - "asn1": "0.2.3", - "assert-plus": "0.2.0", - "dashdash": "1.13.0", - "ecc-jsbn": "0.1.1", - "jodid25519": "1.0.2", - "jsbn": "0.1.0", - "tweetnacl": "0.14.3" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, "string-width": { @@ -2346,9 +2221,9 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz", "integrity": "sha1-ySEptvHX9SrPmvQkom44ZKBc6wo=", "requires": { - "code-point-at": "1.0.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { @@ -2356,17 +2231,12 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "2.0.0" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -2375,7 +2245,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } }, "strip-indent": { @@ -2384,13 +2254,13 @@ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "requires": { - "get-stdin": "4.0.1" + "get-stdin": "^4.0.1" } }, "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, "sumchecker": { @@ -2399,7 +2269,7 @@ "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", "dev": true, "requires": { - "debug": "2.6.9" + "debug": "^2.2.0" }, "dependencies": { "debug": { @@ -2410,12 +2280,6 @@ "requires": { "ms": "2.0.0" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true } } }, @@ -2424,9 +2288,9 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", "requires": { - "mkdirp": "0.5.1", - "pump": "1.0.1", - "tar-stream": "1.5.1" + "mkdirp": "^0.5.0", + "pump": "^1.0.0", + "tar-stream": "^1.1.2" } }, "tar-stream": { @@ -2434,10 +2298,10 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.1.tgz", "integrity": "sha1-UWx00b6j4THMC5NIkpyag/CirRE=", "requires": { - "bl": "1.1.2", - "end-of-stream": "1.1.0", - "readable-stream": "2.0.6", - "xtend": "4.0.1" + "bl": "^1.0.0", + "end-of-stream": "^1.0.0", + "readable-stream": "^2.0.0", + "xtend": "^4.0.0" }, "dependencies": { "isarray": { @@ -2450,12 +2314,12 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.1", - "isarray": "1.0.0", - "process-nextick-args": "1.0.6", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" } } } @@ -2471,8 +2335,8 @@ "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", "dev": true, "requires": { - "readable-stream": "1.1.14", - "xtend": "2.1.2" + "readable-stream": "~1.1.9", + "xtend": "~2.1.1" }, "dependencies": { "xtend": { @@ -2481,7 +2345,7 @@ "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", "dev": true, "requires": { - "object-keys": "0.4.0" + "object-keys": "~0.4.0" } } } @@ -2492,7 +2356,7 @@ "integrity": "sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA=", "dev": true, "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.1" } }, "touch": { @@ -2501,7 +2365,7 @@ "integrity": "sha1-Ua7z1ElXHU8oel2Hyci0kYGg2x0=", "dev": true, "requires": { - "nopt": "1.0.10" + "nopt": "~1.0.10" }, "dependencies": { "nopt": { @@ -2510,17 +2374,18 @@ "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, "requires": { - "abbrev": "1.1.1" + "abbrev": "1" } } } }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { - "punycode": "1.4.1" + "psl": "^1.1.24", + "punycode": "^1.4.1" } }, "traverse": { @@ -2541,7 +2406,7 @@ "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", "dev": true, "requires": { - "utf8-byte-length": "1.0.4" + "utf8-byte-length": "^1.0.1" } }, "tunnel-agent": { @@ -2549,13 +2414,13 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz", - "integrity": "sha1-PaOC9nDyXe1417PReSEZvKC3Ey0=", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, "typedarray": { @@ -2567,8 +2432,7 @@ "universalify": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", - "dev": true + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" }, "utf8-byte-length": { "version": "1.0.4", @@ -2582,9 +2446,9 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "validate-npm-package-license": { "version": "3.0.1", @@ -2592,16 +2456,18 @@ "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", "dev": true, "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.2" + "spdx-correct": "~1.0.0", + "spdx-expression-parse": "~1.0.0" } }, "verror": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", - "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "extsprintf": "1.0.2" + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" } }, "which": { @@ -2609,7 +2475,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "window-size": { @@ -2622,7 +2488,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.0.0.tgz", "integrity": "sha1-fTD4+HP5pbvDpk2ryNF34HGuQm8=", "requires": { - "string-width": "1.0.1" + "string-width": "^1.0.1" } }, "wrappy": { @@ -2657,13 +2523,30 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", "requires": { - "camelcase": "2.1.1", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "os-locale": "1.4.0", - "string-width": "1.0.1", - "window-size": "0.1.4", - "y18n": "3.2.1" + "camelcase": "^2.0.1", + "cliui": "^3.0.3", + "decamelize": "^1.1.1", + "os-locale": "^1.4.0", + "string-width": "^1.0.1", + "window-size": "^0.1.4", + "y18n": "^3.2.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } } }, "yauzl": { @@ -2672,7 +2555,7 @@ "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", "dev": true, "requires": { - "fd-slicer": "1.0.1" + "fd-slicer": "~1.0.1" } } } diff --git a/server-console/package.json b/server-console/package.json index 565658702b..7a93d0faa5 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -8,8 +8,8 @@ "" ], "devDependencies": { - "electron-packager": "^12.0.0", - "electron": "1.8.4" + "electron": "^3.0.0", + "electron-packager": "^12.1.2" }, "repository": { "type": "git", @@ -23,14 +23,15 @@ "packager": "node packager.js" }, "dependencies": { - "always-tail": "0.2.0", - "cheerio": "^0.19.0", + "always-tail": "^0.2.0", + "cheerio": "^0.22.0", + "debug": "^4.0.1", "electron-log": "1.1.1", "extend": "^3.0.0", "fs-extra": "^6.0.0", "node-notifier": "^5.2.1", "os-homedir": "^1.0.1", - "request": "^2.85.0", + "request": "^2.88.0", "request-progress": "1.0.2", "tar-fs": "^1.12.0", "yargs": "^3.30.0" diff --git a/server-console/src/main.js b/server-console/src/main.js index 92ebdbf36c..3316730971 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -266,15 +266,12 @@ process.on('uncaughtException', function(err) { log.error(err.stack); }); -var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) { - // Someone tried to run a second instance, focus the window (if there is one) - return true; -}); +const gotTheLock = app.requestSingleInstanceLock() -if (shouldQuit) { - log.warn("Another instance of the Sandbox is already running - this instance will quit."); - app.exit(0); - return; +if (!gotTheLock) { + log.warn("Another instance of the Sandbox is already running - this instance will quit."); + app.exit(0); + return; } // Check command line arguments to see how to find binaries From f2e69d5c818363e58566ceeca5a3aa75be209bfd Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 20 Sep 2018 17:37:39 -0700 Subject: [PATCH 026/276] Make default bubble-box AvatarData property for efficiency Also a use of getClientGlobalPosition(), etc --- .../src/avatars/AvatarMixerClientData.h | 2 +- .../src/avatars/AvatarMixerSlave.cpp | 6 ++--- libraries/avatars/src/AvatarData.cpp | 24 +++++++++++++++++-- libraries/avatars/src/AvatarData.h | 6 +++++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index ccb0f4001d..492d21a807 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -90,7 +90,7 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; - glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); } + glm::vec3 getPosition() const { return _avatar ? _avatar->getClientGlobalPosition() : glm::vec3(0); } bool isRadiusIgnoring(const QUuid& other) const; void addToRadiusIgnoringSet(const QUuid& other); void removeFromRadiusIgnoringSet(const QUuid& other); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index c8cc4d0c8a..f83bb2d3cb 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -244,7 +244,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // reset the internal state for correct random number distribution distribution.reset(); - // Base number to sort on number previously sent. + // Estimate number to sort on number sent last frame. const int numToSendEst = std::max(nodeData->getNumAvatarsSentLastFrame() * 2, 20); // reset the number of sent avatars @@ -342,8 +342,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored if (destinationNode->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { // 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); + AABox otherNodeBox = avatarClientNodeData->getAvatar().getDefaultBubbleBox(); if (nodeBox.touches(otherNodeBox)) { nodeData->ignoreOther(destinationNode, avatarNode); shouldIgnore = !getsAnyIgnored; @@ -396,7 +395,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); int avatarSpaceAvailable = avatarPacketCapacity; - //avatarSpaceAvailable = 100; int numPacketsSent = 0; const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 73525f69a7..782483a6c1 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -384,8 +384,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent QByteArray avatarDataByteArray((int)byteArraySize, 0); unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); - unsigned char* startPosition = destinationBuffer; - const unsigned char * packetEnd = destinationBuffer + maxDataSize; + const unsigned char* const startPosition = destinationBuffer; + const unsigned char* const packetEnd = destinationBuffer + maxDataSize; AvatarDataPacket::HasFlags includedFlags = 0; @@ -971,6 +971,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _avatarBoundingBoxChanged = now; } + _defaultBubbleBox = computeBubbleBox(); + sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); int numBytesRead = sourceBuffer - startSection; _avatarBoundingBoxRate.increment(numBytesRead); @@ -2916,3 +2918,21 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value[EntityID] = binaryEntityProperties; } } + +const float AvatarData::DEFAULT_BUBBLE_SCALE = 2.4f; // magic number determined empirically + +AABox AvatarData::computeBubbleBox(float bubbleScale) const { + AABox box = AABox(_globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); + glm::vec3 size = box.getScale(); + size *= bubbleScale; + const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3); + size= glm::max(size, MIN_BUBBLE_SCALE); + box.setScaleStayCentered(size); + return box; +} + +AABox AvatarData::getDefaultBubbleBox() const { + AABox bubbleBox(_defaultBubbleBox); + bubbleBox.translate(_globalPosition); + return bubbleBox; +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index df7129b7a5..3ea7631e0c 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1100,6 +1100,7 @@ public: glm::vec3 getClientGlobalPosition() const { return _globalPosition; } AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } + AABox getDefaultBubbleBox() const; /**jsdoc * @function MyAvatar.getAvatarEntityData @@ -1192,6 +1193,9 @@ public: void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; } int getReplicaIndex() { return _replicaIndex; } + static const float DEFAULT_BUBBLE_SCALE; /* = 2.4 */ + AABox computeBubbleBox(float bubbleScale = DEFAULT_BUBBLE_SCALE) const; + signals: /**jsdoc @@ -1425,6 +1429,8 @@ protected: glm::vec3 _globalBoundingBoxDimensions; glm::vec3 _globalBoundingBoxOffset; + AABox _defaultBubbleBox; + mutable ReadWriteLockable _avatarEntitiesLock; AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording From 82b81c907670a0eb26053648bc551045219c6841 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 24 Sep 2018 11:48:48 -0700 Subject: [PATCH 027/276] Limit no. of joints sent per avatar to prevent lock-up With this the avatar mixer will sent the full bit-vector but only the first 111 actual joints. --- libraries/avatars/src/AvatarData.cpp | 10 +++++++--- libraries/avatars/src/AvatarData.h | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 782483a6c1..31687a7f27 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -592,10 +592,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent jointData = _jointData; } const int numJoints = jointData.size(); + assert(numJoints <= 255); const int jointBitVectorSize = calcBitVectorSize(numJoints); + const int cappedNumJoints = std::min(numJoints, AvatarDataPacket::MAX_NUM_JOINTS); // Check against full size or minimum size: count + two bit-vectors + two controllers - const size_t approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(numJoints, true) : + const size_t approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(cappedNumJoints, false) : 1 + 2 * jointBitVectorSize + 2 * (sizeof(AvatarDataPacket::SixByteQuat) + sizeof(AvatarDataPacket::SixByteTrans)); IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { @@ -605,6 +607,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent *destinationBuffer++ = (uint8_t)numJoints; unsigned char* validityPosition = destinationBuffer; + memset(validityPosition, 0, jointBitVectorSize); unsigned char validity = 0; int validityBit = 0; @@ -622,7 +625,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; - for (int i = 0; i < jointData.size(); i++) { + for (int i = 0; i < cappedNumJoints; i++) { const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; @@ -666,12 +669,13 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent unsigned char* beforeTranslations = destinationBuffer; #endif + memset(destinationBuffer, 0, jointBitVectorSize); destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; float maxTranslationDimension = 0.0; - for (int i = 0; i < jointData.size(); i++) { + for (int i = 0; i < cappedNumJoints; i++) { const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 3ea7631e0c..5b5d49feea 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -297,7 +297,8 @@ namespace AvatarDataPacket { const size_t FAR_GRAB_JOINTS_SIZE = 84; static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); - const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; + static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; + static const int MAX_NUM_JOINTS = 111; } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation From 07bdaeede794271a98b56275e9e687b930d0022a Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 24 Sep 2018 18:23:49 -0700 Subject: [PATCH 028/276] Split avatar joint-data across multiple packets if necessary --- .../src/avatars/AvatarMixerSlave.cpp | 16 +-- libraries/avatars/src/AvatarData.cpp | 136 ++++++++---------- libraries/avatars/src/AvatarData.h | 11 +- 3 files changed, 80 insertions(+), 83 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index f83bb2d3cb..3810456f71 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -450,12 +450,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const bool distanceAdjust = true; const bool dropFaceTracking = false; - AvatarDataPacket::HasFlags includeFlags = 0; // the result of the toByteArray + AvatarDataPacket::SendStatus sendStatus; do { auto startSerialize = chrono::high_resolution_clock::now(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - includeFlags, dropFaceTracking, distanceAdjust, myPosition, + sendStatus, dropFaceTracking, distanceAdjust, myPosition, &lastSentJointsForOther, avatarSpaceAvailable); auto endSerialize = chrono::high_resolution_clock::now(); _stats.toByteArrayElapsedTime += @@ -464,14 +464,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); numAvatarDataBytes += bytes.size(); - if (includeFlags != 0 || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { + if (sendStatus.itemFlags != 0 || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); ++numPacketsSent; avatarPacket = NLPacket::create(PacketType::BulkAvatarData); avatarSpaceAvailable = avatarPacketCapacity; } - } while (includeFlags != 0); + } while (!sendStatus); if (detail != AvatarData::NoData) { _stats.numOthersIncluded++; @@ -565,12 +565,12 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin // so we always send a full update for this avatar quint64 start = usecTimestampNow(); - AvatarDataPacket::HasFlags flagsOut; + AvatarDataPacket::SendStatus sendStatus; QVector emptyLastJointSendData { otherAvatar->getJointCount() }; QByteArray avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, false, false, glm::vec3(0), nullptr, 0); + sendStatus, false, false, glm::vec3(0), nullptr, 0); quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); @@ -596,14 +596,14 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr, 0); + sendStatus, true, false, glm::vec3(0), nullptr, 0); if (avatarByteArray.size() > maxAvatarByteArraySize) { qCWarning(avatars) << "Replicated avatar data without facial data still too large for" << otherAvatar->getSessionUUID() << "-" << avatarByteArray.size() << "bytes"; avatarByteArray = otherAvatar->toByteArray(AvatarData::MinimumData, 0, emptyLastJointSendData, - flagsOut, true, false, glm::vec3(0), nullptr, 0); + sendStatus, true, false, glm::vec3(0), nullptr, 0); } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 31687a7f27..f393ca9ba8 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -225,16 +225,16 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro AvatarDataPacket::HasFlags hasFlagsOut = 0; auto lastSentTime = _lastToByteArray; _lastToByteArray = usecTimestampNow(); + AvatarDataPacket::SendStatus sendStatus; auto avatarByteArray = AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), - hasFlagsOut, dropFaceTracking, false, glm::vec3(0), nullptr, - 0, &_outboundDataRate); + sendStatus, dropFaceTracking, false, glm::vec3(0), nullptr, 0, &_outboundDataRate); // Strip UUID return avatarByteArray.right(avatarByteArray.size() - NUM_BYTES_RFC4122_UUID); } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& itemFlags, bool dropFaceTracking, bool distanceAdjust, + AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, int maxDataSize, AvatarDataRate* outboundDataRateOut) const { bool cullSmallChanges = (dataDetail == CullSmallData); @@ -248,7 +248,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { AvatarDataPacket::HasFlags packetStateFlags = 0; - itemFlags = packetStateFlags; + sendStatus.itemFlags = packetStateFlags; QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof(packetStateFlags)); avatarDataByteArray.append((char*) &packetStateFlags, sizeof packetStateFlags); @@ -274,7 +274,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // 3 translations * 6 bytes = 6.48kbps // - auto parentID = getParentID(); + QUuid parentID; glm::mat4 leftFarGrabMatrix; glm::mat4 rightFarGrabMatrix; @@ -283,7 +283,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // Leading flags, to indicate how much data is actually included in the packet... AvatarDataPacket::HasFlags packetStateFlags = 0; - if (itemFlags == 0) { + if (sendStatus.itemFlags == 0) { bool hasAvatarGlobalPosition = true; // always include global position bool hasAvatarOrientation = false; bool hasAvatarBoundingBox = false; @@ -340,13 +340,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) | (hasJointData ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); - itemFlags = packetStateFlags; + sendStatus.itemFlags = packetStateFlags; + sendStatus.rotationsSent = 0; + sendStatus.translationsSent = 0; } else { - packetStateFlags = itemFlags; - if (packetStateFlags & AvatarDataPacket::PACKET_HAS_JOINT_DATA) { - // Force all joints upon continuation, as deltas aren't valid. - sendAll = true; - } + packetStateFlags = sendStatus.itemFlags; if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { packetStateFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; } @@ -372,6 +370,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent packetStateFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; } } + if (packetStateFlags & (AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS | AvatarDataPacket::PACKET_HAS_PARENT_INFO)) { + parentID = getParentID(); + } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID + AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) + @@ -388,6 +389,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const unsigned char* const packetEnd = destinationBuffer + maxDataSize; AvatarDataPacket::HasFlags includedFlags = 0; + AvatarDataPacket::HasFlags extraReturnedFlags = 0; // Packets always have UUID. memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); @@ -594,13 +596,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const int numJoints = jointData.size(); assert(numJoints <= 255); const int jointBitVectorSize = calcBitVectorSize(numJoints); - const int cappedNumJoints = std::min(numJoints, AvatarDataPacket::MAX_NUM_JOINTS); - // Check against full size or minimum size: count + two bit-vectors + two controllers - const size_t approxJointSpace = sendAll ? AvatarDataPacket::maxJointDataSize(cappedNumJoints, false) : - 1 + 2 * jointBitVectorSize + 2 * (sizeof(AvatarDataPacket::SixByteQuat) + sizeof(AvatarDataPacket::SixByteTrans)); - - IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, approxJointSpace) { + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, 1 + 2 * jointBitVectorSize + AvatarDataPacket::FAUX_JOINT_SIZE) { + // Allow for faux joints + translation bit-vector: + static const ptrdiff_t MIN_SIZE_FOR_JOINT = sizeof(AvatarDataPacket::SixByteQuat) + + jointBitVectorSize + AvatarDataPacket::FAUX_JOINT_SIZE; auto startSection = destinationBuffer; // joint rotation data @@ -608,8 +608,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent unsigned char* validityPosition = destinationBuffer; memset(validityPosition, 0, jointBitVectorSize); - unsigned char validity = 0; - int validityBit = 0; #ifdef WANT_DEBUG int rotationSentCount = 0; @@ -625,44 +623,41 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; - for (int i = 0; i < cappedNumJoints; i++) { + int i = sendStatus.rotationsSent; + for (; i < numJoints; ++i) { const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; - if (!data.rotationIsDefaultPose) { - // 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); + if ((packetEnd - destinationBuffer) >= MIN_SIZE_FOR_JOINT) { + if (!data.rotationIsDefaultPose) { + // 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)) { + validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE); #ifdef WANT_DEBUG - rotationSentCount++; + rotationSentCount++; #endif - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - if (sentJointDataOut) { - (*sentJointDataOut)[i].rotation = data.rotation; + if (sentJointDataOut) { + (*sentJointDataOut)[i].rotation = data.rotation; + } } } + } else { + break; } if (sentJointDataOut) { (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; } - if (++validityBit == BITS_IN_BYTE) { - *validityPosition++ = validity; - validityBit = validity = 0; - } - } - if (validityBit != 0) { - *validityPosition++ = validity; } + sendStatus.rotationsSent = i; // joint translation data validityPosition = destinationBuffer; - validity = 0; - validityBit = 0; #ifdef WANT_DEBUG int translationSentCount = 0; @@ -675,44 +670,41 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; float maxTranslationDimension = 0.0; - for (int i = 0; i < cappedNumJoints; i++) { + i = sendStatus.translationsSent; + for (; i < numJoints; ++i) { const JointData& data = jointData[i]; const JointData& last = lastSentJointData[i]; - if (!data.translationIsDefaultPose) { - if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) - || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { - - validity |= (1 << validityBit); + if (packetEnd - destinationBuffer > MIN_SIZE_FOR_JOINT) { + if (!data.translationIsDefaultPose) { + if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) + || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { + validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE); #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) { - (*sentJointDataOut)[i].translation = data.translation; + if (sentJointDataOut) { + (*sentJointDataOut)[i].translation = data.translation; + } } } + } else { + break; } if (sentJointDataOut) { (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; } - if (++validityBit == BITS_IN_BYTE) { - *validityPosition++ = validity; - validityBit = validity = 0; - } - } - - if (validityBit != 0) { - *validityPosition++ = validity; } + sendStatus.translationsSent = i; // faux joints Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); @@ -775,16 +767,14 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } #endif - if (destinationBuffer >= packetEnd) { - // Joint data too large - revert - destinationBuffer = startSection; - includedFlags &= ~(AvatarDataPacket::PACKET_HAS_GRAB_JOINTS | AvatarDataPacket::PACKET_HAS_JOINT_DATA); - } else { - int numBytes = destinationBuffer - startSection; - if (outboundDataRateOut) { - outboundDataRateOut->jointDataRate.increment(numBytes); - outboundDataRateOut->farGrabJointRate.increment(numGrabJointBytes); - } + if (sendStatus.rotationsSent != numJoints || sendStatus.translationsSent != numJoints) { + extraReturnedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; + } + + int numBytes = destinationBuffer - startSection; + if (outboundDataRateOut) { + outboundDataRateOut->jointDataRate.increment(numBytes); + outboundDataRateOut->farGrabJointRate.increment(numGrabJointBytes); } } @@ -812,7 +802,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent memcpy(packetFlagsLocation, &includedFlags, sizeof(includedFlags)); // Return dropped items. - itemFlags = packetStateFlags & ~includedFlags; + sendStatus.itemFlags = (packetStateFlags & ~includedFlags) | extraReturnedFlags; int avatarDataSize = destinationBuffer - startPosition; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 5b5d49feea..3ed90c059a 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -298,7 +298,14 @@ namespace AvatarDataPacket { static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; - static const int MAX_NUM_JOINTS = 111; + static const size_t FAUX_JOINT_SIZE = 2 * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); + + struct SendStatus { + HasFlags itemFlags { 0 }; + int rotationsSent { 0 }; + int translationsSent { 0 }; + operator bool() { return itemFlags == 0; } + }; } const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation @@ -452,7 +459,7 @@ public: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false); virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, + AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, int maxDataSize = 0, AvatarDataRate* outboundDataRateOut = nullptr) const; virtual void doneEncoding(bool cullSmallChanges); From ad6bbc7ff6954461c35da6ddbdef7b71bf7c5c55 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 25 Sep 2018 11:22:44 -0700 Subject: [PATCH 029/276] latest squatty changes --- interface/src/avatar/MyAvatar.cpp | 25 +++++++++++++++++-------- interface/src/avatar/MyAvatar.h | 1 + plugins/oculus/src/OculusHelpers.cpp | 6 ++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index bf684ccd9a..c52e029d94 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -463,6 +463,7 @@ void MyAvatar::update(float deltaTime) { if ((acosHead > 0.98f) && !getIsInSittingState() && (sensorHeadPoseDebug.getTranslation().y < -0.5f)) { qCDebug(interfaceapp) << "we are going to sitting state because it looks like we should" << sensorHeadPoseDebug.getTranslation().y; } + // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) { @@ -3601,9 +3602,9 @@ glm::vec3 MyAvatar::computeCounterBalance() { } else if (counterBalancedCg.y < sitSquatThreshold) { // do a height reset setResetMode(true); - _follow.activate(FollowHelper::Vertical); + //_follow.activate(FollowHelper::Vertical); // disable cg behaviour in this case. - setIsInSittingState(true); + //setIsInSittingState(true); } return counterBalancedCg; } @@ -4059,12 +4060,6 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons bool stepDetected = false; float myScale = myAvatar.getAvatarScale(); - - // debug head hips angle - //glm::vec3 hipsPos = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); - //glm::vec3 headHipsBody = currentHeadPose.getTranslation() - hipsPos; - //qCDebug(interfaceapp) << "head in sensor space " << withinBaseOfSupport(currentHeadPose); - if (myAvatar.getIsInWalkingState()) { stepDetected = true; } else if (myAvatar.getIsInSittingState()) { @@ -4137,6 +4132,20 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if (myAvatar.getHMDLeanRecenterEnabled() && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { + + // debug head hips angle + glm::vec3 headDefaultPos = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); + if (myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPos.y - 0.05f)) { + _squatCount++; + if ((_squatCount > 300) && !isActive(Vertical) && !isActive(Horizontal)) { + activate(Horizontal); + activate(Vertical); + _squatCount = 0; + } + } else { + _squatCount = 0; + } + if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6f3858c5bf..78dc6307e8 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1727,6 +1727,7 @@ private: std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; std::atomic _toggleHipsFollowing { true }; + int _squatCount{ 0 }; }; FollowHelper _follow; diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 511984c657..e543d3ca00 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -79,6 +79,12 @@ private: if (!OVR_SUCCESS(ovr_Initialize(&initParams))) { qCWarning(oculusLog) << "Failed to initialze Oculus SDK" << ovr::getError(); return; + } else { + qCWarning(oculusLog) << "successful init of oculus!!!!!!!!"; + ovrTrackingOrigin fred; + fred = ovr_GetTrackingOriginType(session); + qCWarning(oculusLog) << (int)fred; + } ovrGraphicsLuid luid; From f95ab1b040aca9562fe222b08321a4b5e05cc56a Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 25 Sep 2018 12:07:59 -0700 Subject: [PATCH 030/276] Clean-up & other small changes --- .../src/avatars/AvatarMixerSlave.cpp | 4 +- libraries/avatars/src/AvatarData.cpp | 51 ++++++++++--------- libraries/avatars/src/AvatarData.h | 2 +- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 3810456f71..6a1241756c 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -244,7 +244,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // reset the internal state for correct random number distribution distribution.reset(); - // Estimate number to sort on number sent last frame. + // Estimate number to sort on number sent last frame (with min. of 20). const int numToSendEst = std::max(nodeData->getNumAvatarsSentLastFrame() * 2, 20); // reset the number of sent avatars @@ -464,7 +464,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) avatarPacket->write(bytes); avatarSpaceAvailable -= bytes.size(); numAvatarDataBytes += bytes.size(); - if (sendStatus.itemFlags != 0 || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { + if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { // Weren't able to fit everything. nodeList->sendPacket(std::move(avatarPacket), *destinationNode); ++numPacketsSent; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f393ca9ba8..4295eb66a7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -245,13 +245,17 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent lazyInitHeadData(); ASSERT(maxDataSize == 0 || (size_t)maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE); + // Leading flags, to indicate how much data is actually included in the packet... + AvatarDataPacket::HasFlags wantedFlags = 0; + AvatarDataPacket::HasFlags includedFlags = 0; + AvatarDataPacket::HasFlags extraReturnedFlags = 0; // For partial joint data. + // special case, if we were asked for no data, then just include the flags all set to nothing if (dataDetail == NoData) { - AvatarDataPacket::HasFlags packetStateFlags = 0; - sendStatus.itemFlags = packetStateFlags; + sendStatus.itemFlags = wantedFlags; - QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof(packetStateFlags)); - avatarDataByteArray.append((char*) &packetStateFlags, sizeof packetStateFlags); + QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof wantedFlags); + avatarDataByteArray.append((char*) &wantedFlags, sizeof wantedFlags); return avatarDataByteArray; } @@ -280,10 +284,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent glm::mat4 rightFarGrabMatrix; glm::mat4 mouseFarGrabMatrix; - // Leading flags, to indicate how much data is actually included in the packet... - AvatarDataPacket::HasFlags packetStateFlags = 0; - if (sendStatus.itemFlags == 0) { + // New avatar ... bool hasAvatarGlobalPosition = true; // always include global position bool hasAvatarOrientation = false; bool hasAvatarBoundingBox = false; @@ -324,7 +326,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent hasJointDefaultPoseFlags = hasJointData; } - packetStateFlags = + wantedFlags = (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) @@ -340,17 +342,18 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent | (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0) | (hasJointData ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0); - sendStatus.itemFlags = packetStateFlags; + sendStatus.itemFlags = wantedFlags; sendStatus.rotationsSent = 0; sendStatus.translationsSent = 0; - } else { - packetStateFlags = sendStatus.itemFlags; - if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { - packetStateFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; + } else { // Continuing avatar ... + wantedFlags = sendStatus.itemFlags; + if (wantedFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + // Must send joints for grab joints - + wantedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA; } } - if (packetStateFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { + if (wantedFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) { bool leftValid; leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid); if (!leftValid) { @@ -367,10 +370,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent mouseFarGrabMatrix = glm::mat4(); } if (!(leftValid || rightValid || mouseValid)) { - packetStateFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; + wantedFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS; } } - if (packetStateFlags & (AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS | AvatarDataPacket::PACKET_HAS_PARENT_INFO)) { + if (wantedFlags & (AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS | AvatarDataPacket::PACKET_HAS_PARENT_INFO)) { parentID = getParentID(); } @@ -388,24 +391,22 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const unsigned char* const startPosition = destinationBuffer; const unsigned char* const packetEnd = destinationBuffer + maxDataSize; - AvatarDataPacket::HasFlags includedFlags = 0; - AvatarDataPacket::HasFlags extraReturnedFlags = 0; - // Packets always have UUID. memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); destinationBuffer += NUM_BYTES_RFC4122_UUID; unsigned char * packetFlagsLocation = destinationBuffer; - destinationBuffer += sizeof(packetStateFlags); + destinationBuffer += sizeof(wantedFlags); #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); // If we want an item and there's sufficient space: -#define IF_AVATAR_SPACE(flag, space) \ - if ((packetStateFlags & AvatarDataPacket::flag) && (size_t)(packetEnd - destinationBuffer) >= (size_t)(space) \ - && (includedFlags |= AvatarDataPacket::flag)) +#define IF_AVATAR_SPACE(flag, space) \ + if ((wantedFlags & AvatarDataPacket::flag) \ + && (size_t)(packetEnd - destinationBuffer) >= (size_t)(space) \ + && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { auto startSection = destinationBuffer; @@ -589,7 +590,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } QVector jointData; - if (packetStateFlags & (AvatarDataPacket::PACKET_HAS_JOINT_DATA | AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS)) { + if (wantedFlags & (AvatarDataPacket::PACKET_HAS_JOINT_DATA | AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS)) { QReadLocker readLock(&_jointDataLock); jointData = _jointData; } @@ -802,7 +803,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent memcpy(packetFlagsLocation, &includedFlags, sizeof(includedFlags)); // Return dropped items. - sendStatus.itemFlags = (packetStateFlags & ~includedFlags) | extraReturnedFlags; + sendStatus.itemFlags = (wantedFlags & ~includedFlags) | extraReturnedFlags; int avatarDataSize = destinationBuffer - startPosition; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 3ed90c059a..40334f9b86 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -302,7 +302,7 @@ namespace AvatarDataPacket { struct SendStatus { HasFlags itemFlags { 0 }; - int rotationsSent { 0 }; + int rotationsSent { 0 }; // ie: index of next unsent joint int translationsSent { 0 }; operator bool() { return itemFlags == 0; } }; From 13890f1d16bf2c33945c9ce2aef0c668b276ae09 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 26 Sep 2018 09:47:26 -0700 Subject: [PATCH 031/276] Use local ID in place of UUID in a couple more cases --- .../src/avatars/AvatarMixerClientData.cpp | 10 +++++----- assignment-client/src/avatars/AvatarMixerClientData.h | 10 +++++----- assignment-client/src/avatars/AvatarMixerSlave.cpp | 6 +++--- libraries/avatars/src/AvatarData.cpp | 1 - 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 2226a4c7a6..801ef61abf 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -26,20 +26,20 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID _avatar->setID(nodeID); } -uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const { - std::unordered_map::const_iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); +uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar) const { + const auto itr = _lastOtherAvatarEncodeTime.find(otherAvatar); if (itr != _lastOtherAvatarEncodeTime.end()) { return itr->second; } return 0; } -void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) { - std::unordered_map::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); +void AvatarMixerClientData::setLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar, uint64_t time) { + auto itr = _lastOtherAvatarEncodeTime.find(otherAvatar); if (itr != _lastOtherAvatarEncodeTime.end()) { itr->second = time; } else { - _lastOtherAvatarEncodeTime.emplace(std::pair(otherAvatar, time)); + _lastOtherAvatarEncodeTime.emplace(std::pair(otherAvatar, time)); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 492d21a807..634c202611 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -111,10 +111,10 @@ public: const ConicalViewFrustums& getViewFrustums() const { return _currentViewFrustums; } - uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; - void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time); + uint64_t getLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar) const; + void setLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar, uint64_t time); - QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; } + QVector& getLastOtherAvatarSentJoints(NLPacket::LocalID otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; } void queuePacket(QSharedPointer message, SharedNodePointer node); int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed @@ -152,8 +152,8 @@ private: // this is a map of the last time we encoded an "other" avatar for // sending to "this" node - std::unordered_map _lastOtherAvatarEncodeTime; - std::unordered_map> _lastOtherAvatarSentJoints; + std::unordered_map _lastOtherAvatarEncodeTime; + std::unordered_map> _lastOtherAvatarSentJoints; uint64_t _identityChangeTimestamp; bool _avatarSessionDisplayNameMustChange{ true }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 6a1241756c..5e1e1019e4 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -382,7 +382,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) if (!shouldIgnore) { // sort this one for later const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData(); - auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNodeData->getSessionUUID()); + auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID()); sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); } @@ -446,7 +446,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->incrementAvatarInView(); } - QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); + QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); const bool distanceAdjust = true; const bool dropFaceTracking = false; @@ -482,7 +482,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // set the last sent sequence number for this sender on the receiver nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), otherNodeData->getLastReceivedSequenceNumber()); - nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); + nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); } auto endAvatarDataPacking = chrono::high_resolution_clock::now(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2282745905..91e7939d9c 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -222,7 +222,6 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio // we want to track outbound data in this case... QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) { - AvatarDataPacket::HasFlags hasFlagsOut = 0; auto lastSentTime = _lastToByteArray; _lastToByteArray = usecTimestampNow(); AvatarDataPacket::SendStatus sendStatus; From 1e79329e834bd8f4e50a4e71f67a40694f6f9c84 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 26 Sep 2018 14:23:52 -0700 Subject: [PATCH 032/276] Access joint data via C pointer instead of operator[] QVector::detach() was showing up in the linux profiles - it's related to the copy-on-write capability. Const QVectors don't use it though. Also a bug fix for the minimum joint space needed. --- libraries/avatars/src/AvatarData.cpp | 39 +++++++++++++++------------- libraries/avatars/src/AvatarData.h | 2 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 91e7939d9c..7fb0f786ba 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -402,9 +402,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(src); // If we want an item and there's sufficient space: -#define IF_AVATAR_SPACE(flag, space) \ - if ((wantedFlags & AvatarDataPacket::flag) \ - && (size_t)(packetEnd - destinationBuffer) >= (size_t)(space) \ +#define IF_AVATAR_SPACE(flag, space) \ + if ((wantedFlags & AvatarDataPacket::flag) \ + && (packetEnd - destinationBuffer) >= (ptrdiff_t)(space) \ && (includedFlags |= AvatarDataPacket::flag)) IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { @@ -597,10 +597,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent assert(numJoints <= 255); const int jointBitVectorSize = calcBitVectorSize(numJoints); - IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, 1 + 2 * jointBitVectorSize + AvatarDataPacket::FAUX_JOINT_SIZE) { + // Start joints if room for at least the faux joints. + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, 1 + 2 * jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE) { // Allow for faux joints + translation bit-vector: - static const ptrdiff_t MIN_SIZE_FOR_JOINT = sizeof(AvatarDataPacket::SixByteQuat) - + jointBitVectorSize + AvatarDataPacket::FAUX_JOINT_SIZE; + const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + + jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE; auto startSection = destinationBuffer; // joint rotation data @@ -620,15 +621,17 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (sentJointDataOut) { sentJointDataOut->resize(numJoints); // Make sure the destination is resized before using it } + const JointData *const joints = jointData.data(); + JointData *const sentJoints = sentJointDataOut ? sentJointDataOut->data() : nullptr; float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT; int i = sendStatus.rotationsSent; for (; i < numJoints; ++i) { - const JointData& data = jointData[i]; + const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; - if ((packetEnd - destinationBuffer) >= MIN_SIZE_FOR_JOINT) { + if (packetEnd - destinationBuffer >= minSizeForJoint) { if (!data.rotationIsDefaultPose) { // 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 @@ -640,8 +643,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent #endif destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - if (sentJointDataOut) { - (*sentJointDataOut)[i].rotation = data.rotation; + if (sentJoints) { + sentJoints[i].rotation = data.rotation; } } } @@ -649,8 +652,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent break; } - if (sentJointDataOut) { - (*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose; + if (sentJoints) { + sentJoints[i].rotationIsDefaultPose = data.rotationIsDefaultPose; } } @@ -672,10 +675,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent float maxTranslationDimension = 0.0; i = sendStatus.translationsSent; for (; i < numJoints; ++i) { - const JointData& data = jointData[i]; + const JointData& data = joints[i]; const JointData& last = lastSentJointData[i]; - if (packetEnd - destinationBuffer > MIN_SIZE_FOR_JOINT) { + if (packetEnd - destinationBuffer >= minSizeForJoint) { if (!data.translationIsDefaultPose) { if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { @@ -690,8 +693,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - if (sentJointDataOut) { - (*sentJointDataOut)[i].translation = data.translation; + if (sentJoints) { + sentJoints[i].translation = data.translation; } } } @@ -699,8 +702,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent break; } - if (sentJointDataOut) { - (*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose; + if (sentJoints) { + sentJoints[i].translationIsDefaultPose = data.translationIsDefaultPose; } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a4dfaa594e..3ca8bd4775 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -298,7 +298,7 @@ namespace AvatarDataPacket { static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match."); static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE; - static const size_t FAUX_JOINT_SIZE = 2 * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); + static const size_t FAUX_JOINTS_SIZE = 2 * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); struct SendStatus { HasFlags itemFlags { 0 }; From fa9abf0fff45ba8205a8647bdb28f18bc068e676 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 26 Sep 2018 18:08:00 -0700 Subject: [PATCH 033/276] added the floor at 0.0 in sensor space for oculus. to do: vive --- interface/src/avatar/MyAvatar.cpp | 35 ++++++++++++++++++++-------- interface/src/avatar/MyAvatar.h | 5 ++-- plugins/oculus/src/OculusHelpers.cpp | 13 ++++++----- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6394631484..2152c12b64 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -477,10 +477,20 @@ void MyAvatar::update(float deltaTime) { auto sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); glm::vec3 upHead = transformVectorFast(sensorHeadPoseDebug.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); + qCDebug(interfaceapp) << "sensor space head pos " << sensorHeadPoseDebug.getTranslation().y; if ((acosHead > 0.98f) && !getIsInSittingState() && (sensorHeadPoseDebug.getTranslation().y < -0.5f)) { - qCDebug(interfaceapp) << "we are going to sitting state because it looks like we should" << sensorHeadPoseDebug.getTranslation().y; + //qCDebug(interfaceapp) << "we are going to sitting state because it looks like we should" << sensorHeadPoseDebug.getTranslation().y; } - + if (!_lastFrameHMDMode && qApp->isHMDMode()) { + // we have entered hmd mode, so make the best guess about sitting or standing + if (sensorHeadPoseDebug.getTranslation().y < 1.3f) { + // then we are sitting. + // setIsInSittingState(true); + } else { + // setIsInSittingState(false); + } + } + // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) { @@ -3575,9 +3585,9 @@ glm::vec3 MyAvatar::computeCounterBalance() { } else if (counterBalancedCg.y < sitSquatThreshold) { // do a height reset setResetMode(true); - //_follow.activate(FollowHelper::Vertical); + // _follow.activate(FollowHelper::Vertical); // disable cg behaviour in this case. - //setIsInSittingState(true); + // setIsInSittingState(true); } return counterBalancedCg; } @@ -4090,7 +4100,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl return true; } else if (offset.y > CYLINDER_TOP) { // if we recenter upwards then no longer in sitting state - myAvatar.setIsInSittingState(false); + // myAvatar.setIsInSittingState(false); return true; } else { return false; @@ -4110,15 +4120,20 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat glm::vec3 headDefaultPos = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); if (myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPos.y - 0.05f)) { _squatCount++; - if ((_squatCount > 300) && !isActive(Vertical) && !isActive(Horizontal)) { - activate(Horizontal); - activate(Vertical); - _squatCount = 0; + if ((_squatCount > 600) && !isActive(Vertical) && !isActive(Horizontal)) { + if (myAvatar.getIsInSittingState()) { + // activate(Horizontal); + activate(Vertical); + _squatCount = 0; + } else { + activate(Horizontal); + _squatCount = 0; + } } } else { _squatCount = 0; } - + if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1dabe38116..4ccb1a8d75 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1728,7 +1728,7 @@ private: std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; std::atomic _toggleHipsFollowing { true }; - int _squatCount{ 0 }; + int _squatCount { 0 }; }; FollowHelper _follow; @@ -1760,6 +1760,7 @@ private: glm::quat _customListenOrientation; AtRestDetector _hmdAtRestDetector; + bool _lastFrameHMDMode { false } ; bool _lastIsMoving { false }; // all poses are in sensor-frame @@ -1804,7 +1805,7 @@ private: ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; - bool _isInSittingState { false }; + bool _isInSittingState { true }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index e543d3ca00..38d93d088d 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -79,18 +79,19 @@ private: if (!OVR_SUCCESS(ovr_Initialize(&initParams))) { qCWarning(oculusLog) << "Failed to initialze Oculus SDK" << ovr::getError(); return; - } else { - qCWarning(oculusLog) << "successful init of oculus!!!!!!!!"; - ovrTrackingOrigin fred; - fred = ovr_GetTrackingOriginType(session); - qCWarning(oculusLog) << (int)fred; - } ovrGraphicsLuid luid; if (!OVR_SUCCESS(ovr_Create(&session, &luid))) { qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError(); return; + } else { + qCWarning(oculusLog) << "successful init of oculus!!!!!!!!"; + ovrTrackingOrigin fred; + //fred = ovr_GetTrackingOriginType(session); + ovrResult retTrackingType = ovr_SetTrackingOriginType(session, ovrTrackingOrigin::ovrTrackingOrigin_FloorLevel); + fred = ovr_GetTrackingOriginType(session); + qCWarning(oculusLog) << OVR_SUCCESS(retTrackingType) << (int)fred; } } From 52355e53f1e69ff4032813c13b229c04c6cfdb0d Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 27 Sep 2018 16:44:55 -0700 Subject: [PATCH 034/276] adding the menu item to the avatar app for the sit state. --- interface/resources/qml/hifi/AvatarApp.qml | 1 + .../resources/qml/hifi/avatarapp/Settings.qml | 57 +- interface/src/avatar/MyAvatar.cpp | 14 +- interface/src/avatar/MyAvatar.h | 9 + .../src/avatars-renderer/Avatar.cpp | 1 + scripts/developer/objectOrientedStep.js | 688 ++++++++++++++++++ scripts/system/avatarapp.js | 15 +- 7 files changed, 778 insertions(+), 7 deletions(-) create mode 100644 scripts/developer/objectOrientedStep.js diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index aea5931627..b06a2ca67c 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -252,6 +252,7 @@ Rectangle { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', collisionsEnabled : settings.avatarCollisionsOn, + sittingEnabled : settings.avatarSittingOn, animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, collisionSoundUrl : settings.avatarCollisionSoundUrl }; diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index 71bfbb084d..af76ba04d6 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -20,6 +20,7 @@ Rectangle { property real scaleValue: scaleSlider.value / 10 property alias dominantHandIsLeft: leftHandRadioButton.checked property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked + property alias avatarSittingOn: sitRadiobutton.checked property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text @@ -45,6 +46,12 @@ Rectangle { collisionsDisabledRadioButton.checked = true; } + if (settings.sittingEnabled) { + sitRadiobutton.checked = true; + } else { + standRadioButton.checked = true; + } + avatarAnimationJSON = settings.animGraphUrl; avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; avatarCollisionSoundUrl = settings.collisionSoundUrl; @@ -289,8 +296,56 @@ Rectangle { text: "OFF" boxSize: 20 } - } + + // TextStyle9 + + RalewaySemiBold { + size: 17; + Layout.row: 2 + Layout.column: 0 + + text: "Sitting State" + } + + ButtonGroup { + id: sitStand + } + + HifiControlsUit.RadioButton { + id: sitRadiobutton + + Layout.row: 2 + Layout.column: 1 + Layout.leftMargin: -40 + + ButtonGroup.group: sitStand + checked: true + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Sit" + boxSize: 20 + } + + HifiControlsUit.RadioButton { + id: standRadioButton + + Layout.row: 2 + Layout.column: 2 + Layout.rightMargin: 20 + + ButtonGroup.group: sitStand + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Stand" + boxSize: 20 + } + } + ColumnLayout { id: avatarAnimationLayout anchors.top: handAndCollisions.bottom diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2152c12b64..631a4b0670 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -477,7 +477,7 @@ void MyAvatar::update(float deltaTime) { auto sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); glm::vec3 upHead = transformVectorFast(sensorHeadPoseDebug.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); - qCDebug(interfaceapp) << "sensor space head pos " << sensorHeadPoseDebug.getTranslation().y; + // qCDebug(interfaceapp) << "sensor space head pos " << sensorHeadPoseDebug.getTranslation().y; if ((acosHead > 0.98f) && !getIsInSittingState() && (sensorHeadPoseDebug.getTranslation().y < -0.5f)) { //qCDebug(interfaceapp) << "we are going to sitting state because it looks like we should" << sensorHeadPoseDebug.getTranslation().y; } @@ -3854,6 +3854,7 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { void MyAvatar::setIsInSittingState(bool isSitting) { _isInSittingState = isSitting; + emit sittingEnabledChanged(isSitting); } void MyAvatar::setWalkSpeed(float value) { @@ -4098,9 +4099,9 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl if (offset.y < SITTING_BOTTOM) { // we recenter when sitting. return true; - } else if (offset.y > CYLINDER_TOP) { + } else if (offset.y > 2.0*CYLINDER_TOP) { // if we recenter upwards then no longer in sitting state - // myAvatar.setIsInSittingState(false); + myAvatar.setIsInSittingState(false); return true; } else { return false; @@ -4126,7 +4127,12 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat activate(Vertical); _squatCount = 0; } else { - activate(Horizontal); + if (myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPos.y - 0.20f)) { + myAvatar.setIsInSittingState(true); + activate(Vertical); + } else { + activate(Horizontal); + } _squatCount = 0; } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 4ccb1a8d75..d9744e93fe 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -239,6 +239,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float walkSpeed READ getWalkSpeed WRITE setWalkSpeed); Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed); + Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; @@ -1506,6 +1507,14 @@ signals: */ void disableHandTouchForIDChanged(const QUuid& entityID, bool disable); + /**jsdoc + * Triggered when the sit state is enabled or disabled + * @function MyAvatar.sittingEnabledChanged + * @param {boolean} enabled + * @returns {Signal} + */ + void sittingEnabledChanged(bool enabled); + private slots: void leaveDomain(); void updateCollisionCapsuleCache(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 914a3b7c6e..51c51b6a20 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1618,6 +1618,7 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { glm::vec3 Avatar::getWorldFeetPosition() { ShapeInfo shapeInfo; + computeShapeInfo(shapeInfo); glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight glm::vec3 localFeet(0.0f, shapeInfo.getOffset().y - halfExtents.y - halfExtents.x, 0.0f); diff --git a/scripts/developer/objectOrientedStep.js b/scripts/developer/objectOrientedStep.js new file mode 100644 index 0000000000..a5c27e36b9 --- /dev/null +++ b/scripts/developer/objectOrientedStep.js @@ -0,0 +1,688 @@ +/* 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(); +}); diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 10ccb66d96..ee4c6736a2 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -63,7 +63,8 @@ function getMyAvatar() { function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), - collisionsEnabled : MyAvatar.getCollisionsEnabled(), + collisionsEnabled: MyAvatar.getCollisionsEnabled(), + sittingEnabled: MyAvatar.isInSittingState, collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), @@ -136,6 +137,13 @@ function onCollisionsEnabledChanged(enabled) { } } +function onSittingEnabledChanged(isSitting) { + if (currentAvatarSettings.sittingEnabled !== isSitting) { + currentAvatarSettings.sittingEnabled = isSitting; + sendToQml({ 'method': 'settingChanged', 'name': 'sittingEnabled', 'value': isSitting }) + } +} + function onNewCollisionSoundUrl(url) { if(currentAvatarSettings.collisionSoundUrl !== url) { currentAvatarSettings.collisionSoundUrl = url; @@ -314,9 +322,10 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See MyAvatar.setDominantHand(message.settings.dominantHand); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); + MyAvatar.isInSittingState = message.settings.sittingEnabled; MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); - + print("save settings"); settings = getMyAvatarSettings(); break; default: @@ -507,6 +516,7 @@ function off() { MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); + MyAvatar.sittingEnabledChanged.disconnect(onSittingEnabledChanged); MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); @@ -521,6 +531,7 @@ function on() { MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); + MyAvatar.sittingEnabledChanged.connect(onSittingEnabledChanged); MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); From 42cb8a7ef0159f130c65d6a424bf8b54ae14323e Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 27 Sep 2018 16:59:54 -0700 Subject: [PATCH 035/276] avatar app changes --- interface/src/avatar/MyAvatar.cpp | 2 +- scripts/system/avatarapp.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 631a4b0670..d54616f245 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -477,7 +477,7 @@ void MyAvatar::update(float deltaTime) { auto sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); glm::vec3 upHead = transformVectorFast(sensorHeadPoseDebug.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); - // qCDebug(interfaceapp) << "sensor space head pos " << sensorHeadPoseDebug.getTranslation().y; + // qCDebug(interfaceapp) << "sensor space head pos " << sensorHeadPoseDebug.getTranslation().y; if ((acosHead > 0.98f) && !getIsInSittingState() && (sensorHeadPoseDebug.getTranslation().y < -0.5f)) { //qCDebug(interfaceapp) << "we are going to sitting state because it looks like we should" << sensorHeadPoseDebug.getTranslation().y; } diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index ee4c6736a2..16761f29a6 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -140,6 +140,7 @@ function onCollisionsEnabledChanged(enabled) { function onSittingEnabledChanged(isSitting) { if (currentAvatarSettings.sittingEnabled !== isSitting) { currentAvatarSettings.sittingEnabled = isSitting; + print("emit sitting changed"); sendToQml({ 'method': 'settingChanged', 'name': 'sittingEnabled', 'value': isSitting }) } } From 7a0043c01002e359e291b931eaaa166de87eb846 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 28 Sep 2018 10:26:52 -0700 Subject: [PATCH 036/276] Send AvatarIdentity in NLPacketList; be more selective --- .../src/avatars/AvatarMixerSlave.cpp | 37 +++++++++++-------- .../src/avatars/AvatarMixerSlave.h | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 5e1e1019e4..3834516b80 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -68,13 +68,13 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { _stats.processIncomingPacketsElapsedTime += (end - start); } -int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { - if (destinationNode->getType() == NodeType::Agent && !destinationNode->isUpstream()) { +int AvatarMixerSlave::sendIdentityPacket(NLPacketList& packetList, const AvatarMixerClientData* nodeData, const Node& destinationNode) { + if (destinationNode.getType() == NodeType::Agent && !destinationNode.isUpstream()) { QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious - auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); - identityPackets->write(individualData); - DependencyManager::get()->sendPacketList(std::move(identityPackets), *destinationNode); + packetList.startSegment(); + packetList.write(individualData); + packetList.endSegment(); _stats.numIdentityPackets++; return individualData.size(); } else { @@ -396,6 +396,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); int avatarSpaceAvailable = avatarPacketCapacity; int numPacketsSent = 0; + auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); for (const auto& sortedAvatar : sortedAvatarVector) { @@ -425,16 +426,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); - // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO - // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. - if (otherAvatar->hasProcessedFirstIdentity() - && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { - identityBytesSent += sendIdentityPacket(otherNodeData, node); - - // remember the last time we sent identity details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); - } - // Typically all out-of-view avatars but such avatars' priorities will rise with time: bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; @@ -444,6 +435,16 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) } else if (!overBudget) { detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; nodeData->incrementAvatarInView(); + + // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO + // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. + if (otherAvatar->hasProcessedFirstIdentity() + && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { + identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode); + + // remember the last time we sent identity details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); + } } QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); @@ -520,6 +521,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode); } + // Send any AvatarIdentity packets: + identityPacketList->closeCurrentPacket(); + if (identityBytesSent > 0) { + nodeList->sendPacketList(std::move(identityPacketList), *destinationNode); + } + // record the number of avatars held back this frame nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index bcb70f8743..2ef90af38e 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -101,7 +101,7 @@ public: void harvestStats(AvatarMixerSlaveStats& stats); private: - int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); + int sendIdentityPacket(NLPacketList& packet, const AvatarMixerClientData* nodeData, const Node& destinationNode); int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode); qint64 addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, From 884ad66a146541fba08975af43e8cbfe1e46cd16 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 28 Sep 2018 17:18:24 -0700 Subject: [PATCH 037/276] added sensor space detection of height of the user in MyAvatar.cpp 1.2 * average sensor space height changes to standing. .833 sensorspace height changes to sitting. there is also a manual override in the avatar app settings now --- interface/src/avatar/MyAvatar.cpp | 38 +++++++++++++++++++++++++------ interface/src/avatar/MyAvatar.h | 4 +++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d54616f245..819778b1e7 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -475,6 +475,12 @@ void MyAvatar::update(float deltaTime) { // if the head is close to the floor in sensor space // and the up of the head is close to sensor up then try switching us to sitting state. auto sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); + if (sensorHeadPoseDebug.isValid()) { + _sumUserHeightSensorSpace += sensorHeadPoseDebug.getTranslation().y; + _averageUserHeightCount++; + } + qCDebug(interfaceapp) << sensorHeadPoseDebug.isValid() << " valid. sensor space average " << (_sumUserHeightSensorSpace / _averageUserHeightCount) << " head position sensor y value " << sensorHeadPoseDebug.getTranslation().y; + glm::vec3 upHead = transformVectorFast(sensorHeadPoseDebug.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); // qCDebug(interfaceapp) << "sensor space head pos " << sensorHeadPoseDebug.getTranslation().y; @@ -3854,6 +3860,9 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { void MyAvatar::setIsInSittingState(bool isSitting) { _isInSittingState = isSitting; + controller::Pose sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); + _sumUserHeightSensorSpace = sensorHeadPoseDebug.getTranslation().y; + _averageUserHeightCount = 1; emit sittingEnabledChanged(isSitting); } @@ -4093,21 +4102,36 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl glm::vec3 headWorldSpace = myAvatar.getHead()->getPosition(); glm::mat4 worldToSensorMatrix = glm::inverse(myAvatar.getSensorToWorldMatrix()); glm::vec3 headSensorSpace = transformVectorFast(myAvatar.getSensorToWorldMatrix(), headWorldSpace); - //qCDebug(interfaceapp) << "sensor space position " << extractTranslation(currentBodyMatrix) << " head position sensor " << sensorHeadPose.getTranslation(); + //get the mode. + //put it in sensor space. + // if we are 20% higher switch to standing. + // 16.6% lower then switch to sitting. + // add this !!!! And the head is upright. + float averageSensorSpaceHeight = myAvatar._sumUserHeightSensorSpace / myAvatar._averageUserHeightCount; if (myAvatar.getIsInSittingState()) { if (offset.y < SITTING_BOTTOM) { // we recenter when sitting. return true; - } else if (offset.y > 2.0*CYLINDER_TOP) { + } else if (sensorHeadPose.getTranslation().y > (1.2f * averageSensorSpaceHeight)) { // if we recenter upwards then no longer in sitting state myAvatar.setIsInSittingState(false); + //myAvatar._sumUserHeightSensorSpace = 1.2f * averageSensorSpaceHeight; + // myAvatar._averageUserHeightCount = 1; return true; } else { return false; } } else { - return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + // in the standing state + if (sensorHeadPose.getTranslation().y < (0.83f * averageSensorSpaceHeight)) { + myAvatar.setIsInSittingState(true); + // myAvatar._sumUserHeightSensorSpace = 0.83f * averageSensorSpaceHeight; + // myAvatar._averageUserHeightCount = 1; + return true; + } else { + return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + } } } @@ -4124,14 +4148,14 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if ((_squatCount > 600) && !isActive(Vertical) && !isActive(Horizontal)) { if (myAvatar.getIsInSittingState()) { // activate(Horizontal); - activate(Vertical); + //activate(Vertical); _squatCount = 0; } else { if (myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPos.y - 0.20f)) { - myAvatar.setIsInSittingState(true); - activate(Vertical); + //myAvatar.setIsInSittingState(true); + //activate(Vertical); } else { - activate(Horizontal); + //activate(Horizontal); } _squatCount = 0; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d9744e93fe..628c358202 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1805,6 +1805,8 @@ private: // height of user in sensor space, when standing erect. ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; + float _sumUserHeightSensorSpace { DEFAULT_AVATAR_HEIGHT }; + int _averageUserHeightCount { 1 }; void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); @@ -1814,7 +1816,7 @@ private: ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; - bool _isInSittingState { true }; + bool _isInSittingState { false }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; From 18c1371321cea741e2c9b9ec6f49eef0759ca3b7 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 28 Sep 2018 17:53:06 -0700 Subject: [PATCH 038/276] Process multiple avatars in an AvatarIdentity message --- assignment-client/src/avatars/AvatarMixer.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 21 +++--- libraries/avatars/src/AvatarData.h | 2 +- libraries/avatars/src/AvatarHashMap.cpp | 70 ++++++++++--------- 4 files changed, 50 insertions(+), 45 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index ebd86b749b..168c95cb77 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -565,7 +565,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes // parse the identity packet and update the change timestamp if appropriate bool identityChanged = false; bool displayNameChanged = false; - avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); + avatar.processAvatarIdentity(QDataStream(message->getMessage()), identityChanged, displayNameChanged); if (identityChanged) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 7fb0f786ba..07751d03e3 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1767,11 +1767,9 @@ glm::quat AvatarData::getOrientationOutbound() const { return (getLocalOrientation()); } -void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, +void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged) { - QDataStream packetStream(identityData); - QUuid avatarSessionID; // peek the sequence number, this will tell us if we should be processing this identity packet at all @@ -1786,17 +1784,18 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide << (udt::SequenceNumber::Type) incomingSequenceNumber; } - if (incomingSequenceNumber > _identitySequenceNumber) { - Identity identity; + Identity identity; - packetStream - >> identity.attachmentData - >> identity.displayName - >> identity.sessionDisplayName - >> identity.isReplicated - >> identity.lookAtSnappingEnabled + packetStream + >> identity.attachmentData + >> identity.displayName + >> identity.sessionDisplayName + >> identity.isReplicated + >> identity.lookAtSnappingEnabled ; + if (incomingSequenceNumber > _identitySequenceNumber) { + // set the store identity sequence number to match the incoming identity _identitySequenceNumber = incomingSequenceNumber; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 3ca8bd4775..76b699a3d2 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -967,7 +967,7 @@ public: // identityChanged returns true if identity has changed, false otherwise. // identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange. - void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); + void processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged); qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index d205a915f8..79ba5d06f9 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -70,7 +70,7 @@ void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArr if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { - avatar->processAvatarIdentity(identityData, identityChanged, displayNameChanged); + avatar->processAvatarIdentity(QDataStream(identityData), identityChanged, displayNameChanged); } } } @@ -266,41 +266,47 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode) { + QDataStream avatarIdentityStream(message->getMessage()); - // peek the avatar UUID from the incoming packet - QUuid identityUUID = QUuid::fromRfc4122(message->peek(NUM_BYTES_RFC4122_UUID)); + while (!avatarIdentityStream.atEnd()) { + // peek the avatar UUID from the incoming packet + avatarIdentityStream.startTransaction(); + QUuid identityUUID; + avatarIdentityStream >> identityUUID; + avatarIdentityStream.rollbackTransaction(); - if (identityUUID.isNull()) { - qCDebug(avatars) << "Refusing to process identity packet for null avatar ID"; - return; - } - - // make sure this isn't for an ignored avatar - auto nodeList = DependencyManager::get(); - static auto EMPTY = QUuid(); - - { - QReadLocker locker(&_hashLock); - _pendingAvatars.remove(identityUUID); - auto me = _avatarHash.find(EMPTY); - if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) { - // We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an - // identity packet for ourself (such as when we are assigned a sessionDisplayName by the mixer upon joining), - // we make things match here. - identityUUID = EMPTY; + if (identityUUID.isNull()) { + qCDebug(avatars) << "Refusing to process identity packet for null avatar ID"; + return; } - } - - if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) { - // mesh URL for a UUID, find avatar in our list - bool isNewAvatar; - auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); - bool identityChanged = false; - bool displayNameChanged = false; - // In this case, the "sendingNode" is the Avatar Mixer. - avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); - _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); + // make sure this isn't for an ignored avatar + auto nodeList = DependencyManager::get(); + static auto EMPTY = QUuid(); + + { + QReadLocker locker(&_hashLock); + _pendingAvatars.remove(identityUUID); + auto me = _avatarHash.find(EMPTY); + if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) { + // We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an + // identity packet for ourself (such as when we are assigned a sessionDisplayName by the mixer upon joining), + // we make things match here. + identityUUID = EMPTY; + } + } + + if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) { + // mesh URL for a UUID, find avatar in our list + bool isNewAvatar; + auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); + bool identityChanged = false; + bool displayNameChanged = false; + // In this case, the "sendingNode" is the Avatar Mixer. + avatar->processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged); + _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); + + } } } From 4534b87ea07c934e927b70a6fd23c8d7f3d3dc84 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 28 Sep 2018 18:24:59 -0700 Subject: [PATCH 039/276] Fix error passing rvalue ref as lvalue --- libraries/avatars/src/AvatarHashMap.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 79ba5d06f9..e453f5d298 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -69,8 +69,9 @@ void AvatarReplicas::removeReplicas(const QUuid& parentID) { void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; + QDataStream identityDataStream(identityData); for (auto avatar : replicas) { - avatar->processAvatarIdentity(QDataStream(identityData), identityChanged, displayNameChanged); + avatar->processAvatarIdentity(identityDataStream, identityChanged, displayNameChanged); } } } From 3a2a8fe4f8d4dfa7eb965fa4dff59d2914b207d3 Mon Sep 17 00:00:00 2001 From: amantley Date: Sat, 29 Sep 2018 15:49:11 -0700 Subject: [PATCH 040/276] adjusted openvr to use 0.0 for the floor instead of -1.6 --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index fae2144caf..1638a17631 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -463,7 +463,7 @@ bool OpenVrDisplayPlugin::internalActivate() { auto chaperone = vr::VRChaperone(); if (chaperone) { float const UI_RADIUS = 1.0f; - float const UI_HEIGHT = 1.6f; + float const UI_HEIGHT = 0.0f; float const UI_Z_OFFSET = 0.5; float xSize, zSize; From 756d1a6fc4fd8fab92e060cce70bc201165a20f2 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Sat, 29 Sep 2018 21:15:10 -0700 Subject: [PATCH 041/276] Fix another bind-to-temporary --- assignment-client/src/avatars/AvatarMixer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 168c95cb77..48f3edb94f 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -565,7 +565,8 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes // parse the identity packet and update the change timestamp if appropriate bool identityChanged = false; bool displayNameChanged = false; - avatar.processAvatarIdentity(QDataStream(message->getMessage()), identityChanged, displayNameChanged); + QDataStream avatarIdentityStream(message->getMessage()); + avatar.processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged); if (identityChanged) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); From 9e400459270ad90fd896d75025ca7cf5c60a109d Mon Sep 17 00:00:00 2001 From: amantley Date: Sun, 30 Sep 2018 17:13:34 -0700 Subject: [PATCH 042/276] added criteria to stop false positive for sit down --- interface/src/avatar/MyAvatar.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 02ea49cda0..f281d8c87d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4079,13 +4079,21 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl // if we are 20% higher switch to standing. // 16.6% lower then switch to sitting. // add this !!!! And the head is upright. + glm::vec3 upHead = transformVectorFast(sensorHeadPose.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); + float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); + glm::vec3 avatarHips = myAvatar.getAbsoluteJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); + glm::vec3 worldHips = transformVectorFast(myAvatar.getTransform().getMatrix(),avatarHips); + glm::vec3 sensorHips = transformVectorFast(myAvatar.getSensorToWorldMatrix(), worldHips); float averageSensorSpaceHeight = myAvatar._sumUserHeightSensorSpace / myAvatar._averageUserHeightCount; + // we could add a counting here to make sure that a lean forward doesn't accidentally put you in sitting mode. + // but maybe so what. + // the real test is... can I pick something up in standing mode? if (myAvatar.getIsInSittingState()) { if (offset.y < SITTING_BOTTOM) { // we recenter when sitting. return true; - } else if (sensorHeadPose.getTranslation().y > (1.2f * averageSensorSpaceHeight)) { + } else if (sensorHeadPose.getTranslation().y > (1.2f * averageSensorSpaceHeight)) { // if we recenter upwards then no longer in sitting state myAvatar.setIsInSittingState(false); //myAvatar._sumUserHeightSensorSpace = 1.2f * averageSensorSpaceHeight; @@ -4096,7 +4104,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl } } else { // in the standing state - if (sensorHeadPose.getTranslation().y < (0.83f * averageSensorSpaceHeight)) { + if ((sensorHeadPose.getTranslation().y < (0.83f * averageSensorSpaceHeight)) && (acosHead > 0.98f) && !(sensorHips.y > (0.4f * averageSensorSpaceHeight)) { myAvatar.setIsInSittingState(true); // myAvatar._sumUserHeightSensorSpace = 0.83f * averageSensorSpaceHeight; // myAvatar._averageUserHeightCount = 1; From fe7c14e5cf3523beb1be9cb1d5b0feb23188deb5 Mon Sep 17 00:00:00 2001 From: amantley Date: Sun, 30 Sep 2018 17:15:07 -0700 Subject: [PATCH 043/276] removed extraneous script --- 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 1a0e2c6ea181bbae814554967ed8b60729eb56b8 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 1 Oct 2018 08:30:51 -0700 Subject: [PATCH 044/276] clean up for pr --- interface/resources/qml/hifi/avatarapp/Settings.qml | 3 +-- interface/src/avatar/MyAvatar.cpp | 12 ------------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index af76ba04d6..c4289ca650 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -296,7 +296,6 @@ Rectangle { text: "OFF" boxSize: 20 } - // TextStyle9 @@ -345,7 +344,7 @@ Rectangle { boxSize: 20 } } - + ColumnLayout { id: avatarAnimationLayout anchors.top: handAndCollisions.bottom diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f281d8c87d..1bd8d4b2f0 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -485,18 +485,6 @@ void MyAvatar::update(float deltaTime) { glm::vec3 upHead = transformVectorFast(sensorHeadPoseDebug.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); // qCDebug(interfaceapp) << "sensor space head pos " << sensorHeadPoseDebug.getTranslation().y; - if ((acosHead > 0.98f) && !getIsInSittingState() && (sensorHeadPoseDebug.getTranslation().y < -0.5f)) { - //qCDebug(interfaceapp) << "we are going to sitting state because it looks like we should" << sensorHeadPoseDebug.getTranslation().y; - } - if (!_lastFrameHMDMode && qApp->isHMDMode()) { - // we have entered hmd mode, so make the best guess about sitting or standing - if (sensorHeadPoseDebug.getTranslation().y < 1.3f) { - // then we are sitting. - // setIsInSittingState(true); - } else { - // setIsInSittingState(false); - } - } // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter From 90feeffa9db5076f3ca7e2bf8012d60ad11b2c1e Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 1 Oct 2018 14:21:00 -0700 Subject: [PATCH 045/276] cleaning up. putting squat fix in vertical recenter --- interface/src/avatar/MyAvatar.cpp | 70 +++++++++++++--------------- interface/src/avatar/MyAvatar.h | 6 +-- plugins/oculus/src/OculusHelpers.cpp | 10 ++-- scripts/system/avatarapp.js | 6 +-- 4 files changed, 43 insertions(+), 49 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1bd8d4b2f0..278d2703ab 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -484,6 +484,19 @@ void MyAvatar::update(float deltaTime) { glm::vec3 upHead = transformVectorFast(sensorHeadPoseDebug.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); + + glm::vec3 headDefaultPositionAvatarSpace = getAbsoluteDefaultJointTranslationInObjectFrame(getJointIndex("Head")); + glm::quat spine2OrientationAvatarSpace = getAbsoluteJointRotationInObjectFrame(getJointIndex("Spine2")); + glm::vec3 headCurrentPositionAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation(); + glm::vec3 upSpine2 = spine2OrientationAvatarSpace * glm::vec3(0.0f, 1.0f, 0.0f); + float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); + if (headCurrentPositionAvatarSpace.y < (headDefaultPositionAvatarSpace.y - 0.05) && (angleSpine2 > 0.98f)) { + _squatCount++; + } else { + _squatCount = 0; + } + + // qCDebug(interfaceapp) << "sensor space head pos " << sensorHeadPoseDebug.getTranslation().y; // put the average hand azimuth into sensor space. @@ -3795,7 +3808,7 @@ bool MyAvatar::getIsInWalkingState() const { } bool MyAvatar::getIsInSittingState() const { - return _isInSittingState; + return _isInSittingState.get(); } float MyAvatar::getWalkSpeed() const { @@ -3819,7 +3832,7 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { } void MyAvatar::setIsInSittingState(bool isSitting) { - _isInSittingState = isSitting; + _isInSittingState.set(isSitting); controller::Pose sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); _sumUserHeightSensorSpace = sensorHeadPoseDebug.getTranslation().y; _averageUserHeightCount = 1; @@ -4064,8 +4077,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl glm::vec3 headSensorSpace = transformVectorFast(myAvatar.getSensorToWorldMatrix(), headWorldSpace); //get the mode. //put it in sensor space. - // if we are 20% higher switch to standing. - // 16.6% lower then switch to sitting. + // if we are 20% higher switch to standing. + // 16.6% lower then switch to sitting. // add this !!!! And the head is upright. glm::vec3 upHead = transformVectorFast(sensorHeadPose.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); @@ -4074,8 +4087,9 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl glm::vec3 sensorHips = transformVectorFast(myAvatar.getSensorToWorldMatrix(), worldHips); float averageSensorSpaceHeight = myAvatar._sumUserHeightSensorSpace / myAvatar._averageUserHeightCount; // we could add a counting here to make sure that a lean forward doesn't accidentally put you in sitting mode. - // but maybe so what. + // but maybe so what. // the real test is... can I pick something up in standing mode? + if (myAvatar.getIsInSittingState()) { if (offset.y < SITTING_BOTTOM) { @@ -4084,22 +4098,27 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl } else if (sensorHeadPose.getTranslation().y > (1.2f * averageSensorSpaceHeight)) { // if we recenter upwards then no longer in sitting state myAvatar.setIsInSittingState(false); - //myAvatar._sumUserHeightSensorSpace = 1.2f * averageSensorSpaceHeight; - // myAvatar._averageUserHeightCount = 1; return true; } else { return false; } } else { - // in the standing state - if ((sensorHeadPose.getTranslation().y < (0.83f * averageSensorSpaceHeight)) && (acosHead > 0.98f) && !(sensorHips.y > (0.4f * averageSensorSpaceHeight)) { - myAvatar.setIsInSittingState(true); - // myAvatar._sumUserHeightSensorSpace = 0.83f * averageSensorSpaceHeight; - // myAvatar._averageUserHeightCount = 1; - return true; + // in the standing state + if ((sensorHeadPose.getTranslation().y < (0.83f * averageSensorSpaceHeight)) && (acosHead > 0.98f) && !(sensorHips.y > (0.4f * averageSensorSpaceHeight))) { + myAvatar._sitStandStateCount++; + if (myAvatar._sitStandStateCount > 300) { + myAvatar.setIsInSittingState(true); + myAvatar._sitStandStateCount = 0; + myAvatar._squatCount = 0; + return true; + } } else { - return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + if (myAvatar._squatCount > 600) { + return true; + myAvatar._squatCount = 0; + } } + return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); } } @@ -4109,29 +4128,6 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if (myAvatar.getHMDLeanRecenterEnabled() && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { - // debug head hips angle - glm::vec3 headDefaultPos = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); - if (myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPos.y - 0.05f)) { - _squatCount++; - if ((_squatCount > 600) && !isActive(Vertical) && !isActive(Horizontal)) { - if (myAvatar.getIsInSittingState()) { - // activate(Horizontal); - //activate(Vertical); - _squatCount = 0; - } else { - if (myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPos.y - 0.20f)) { - //myAvatar.setIsInSittingState(true); - //activate(Vertical); - } else { - //activate(Horizontal); - } - _squatCount = 0; - } - } - } else { - _squatCount = 0; - } - if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 62ba8d68b1..c7f6fa89ae 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1737,7 +1737,6 @@ private: std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; std::atomic _toggleHipsFollowing { true }; - int _squatCount { 0 }; }; FollowHelper _follow; @@ -1770,7 +1769,6 @@ private: glm::quat _customListenOrientation; AtRestDetector _hmdAtRestDetector; - bool _lastFrameHMDMode { false } ; bool _lastIsMoving { false }; // all poses are in sensor-frame @@ -1817,7 +1815,9 @@ private: ThreadSafeValueCache _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR }; float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; - bool _isInSittingState { false }; + ThreadSafeValueCache _isInSittingState { false }; + int _sitStandStateCount { 0 }; + int _squatCount { 0 }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 38d93d088d..402b05f39c 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -86,12 +86,10 @@ private: qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError(); return; } else { - qCWarning(oculusLog) << "successful init of oculus!!!!!!!!"; - ovrTrackingOrigin fred; - //fred = ovr_GetTrackingOriginType(session); - ovrResult retTrackingType = ovr_SetTrackingOriginType(session, ovrTrackingOrigin::ovrTrackingOrigin_FloorLevel); - fred = ovr_GetTrackingOriginType(session); - qCWarning(oculusLog) << OVR_SUCCESS(retTrackingType) << (int)fred; + ovrResult setFloorLevelOrigin = ovr_SetTrackingOriginType(session, ovrTrackingOrigin::ovrTrackingOrigin_FloorLevel); + if (!OVR_SUCCESS(setFloorLevelOrigin)) { + qCWarning(oculusLog) << "Failed to set the Oculus tracking origin to floor level" << ovr::getError(); + } } } diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 16761f29a6..faf624392a 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -63,8 +63,8 @@ function getMyAvatar() { function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), - collisionsEnabled: MyAvatar.getCollisionsEnabled(), - sittingEnabled: MyAvatar.isInSittingState, + collisionsEnabled : MyAvatar.getCollisionsEnabled(), + sittingEnabled : MyAvatar.isInSittingState, collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), @@ -326,7 +326,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See MyAvatar.isInSittingState = message.settings.sittingEnabled; MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); - print("save settings"); + settings = getMyAvatarSettings(); break; default: From f0676d796c74a8464954bf1a8b7aae3e40df00a0 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 1 Oct 2018 17:57:00 -0700 Subject: [PATCH 046/276] added constants and tipping point for running average. this should be replaced by the mode --- interface/src/avatar/MyAvatar.cpp | 86 +++++++++++++------------------ interface/src/avatar/MyAvatar.h | 2 + 2 files changed, 38 insertions(+), 50 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 278d2703ab..4f28ae8e90 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -471,34 +471,6 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); - // qCDebug(interfaceapp) << "sitting state is " << getIsInSittingState(); - // debug setting for sitting state if you start in the chair. - // if the head is close to the floor in sensor space - // and the up of the head is close to sensor up then try switching us to sitting state. - auto sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); - if (sensorHeadPoseDebug.isValid()) { - _sumUserHeightSensorSpace += sensorHeadPoseDebug.getTranslation().y; - _averageUserHeightCount++; - } - qCDebug(interfaceapp) << sensorHeadPoseDebug.isValid() << " valid. sensor space average " << (_sumUserHeightSensorSpace / _averageUserHeightCount) << " head position sensor y value " << sensorHeadPoseDebug.getTranslation().y; - - glm::vec3 upHead = transformVectorFast(sensorHeadPoseDebug.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); - float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); - - glm::vec3 headDefaultPositionAvatarSpace = getAbsoluteDefaultJointTranslationInObjectFrame(getJointIndex("Head")); - glm::quat spine2OrientationAvatarSpace = getAbsoluteJointRotationInObjectFrame(getJointIndex("Spine2")); - glm::vec3 headCurrentPositionAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation(); - glm::vec3 upSpine2 = spine2OrientationAvatarSpace * glm::vec3(0.0f, 1.0f, 0.0f); - float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); - if (headCurrentPositionAvatarSpace.y < (headDefaultPositionAvatarSpace.y - 0.05) && (angleSpine2 > 0.98f)) { - _squatCount++; - } else { - _squatCount = 0; - } - - - // qCDebug(interfaceapp) << "sensor space head pos " << sensorHeadPoseDebug.getTranslation().y; - // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) { @@ -527,6 +499,29 @@ void MyAvatar::update(float deltaTime) { setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD))); setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); + // if the head tracker reading is valid then add it to the running average. + auto sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); + if (sensorHeadPoseDebug.isValid()) { + _sumUserHeightSensorSpace += sensorHeadPoseDebug.getTranslation().y; + _averageUserHeightCount++; + } + + // if the spine is straight and the head is below the default position by 5 cm then increment squatty count. + const float SQUAT_THRESHOLD = 0.05f; + const float COSINE_TEN_DEGREES = 0.98f; + glm::vec3 headDefaultPositionAvatarSpace = getAbsoluteDefaultJointTranslationInObjectFrame(getJointIndex("Head")); + glm::quat spine2OrientationAvatarSpace = getAbsoluteJointRotationInObjectFrame(getJointIndex("Spine2")); + glm::vec3 upSpine2 = spine2OrientationAvatarSpace * glm::vec3(0.0f, 1.0f, 0.0f); + if (glm::length(upSpine2) > 0.0f) { + upSpine2 = glm::normalize(upSpine2); + } + float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); + if (newHeightReading < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && (angleSpine2 > COSINE_TEN_DEGREES)) { + _squatCount++; + } else { + _squatCount = 0; + } + if (_drawAverageFacingEnabled) { auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation()); @@ -3561,12 +3556,6 @@ glm::vec3 MyAvatar::computeCounterBalance() { if (counterBalancedCg.y > (tposeHips.y + 0.05f)) { // if the height is higher than default hips, clamp to default hips counterBalancedCg.y = tposeHips.y + 0.05f; - } else if (counterBalancedCg.y < sitSquatThreshold) { - // do a height reset - setResetMode(true); - // _follow.activate(FollowHelper::Vertical); - // disable cg behaviour in this case. - // setIsInSittingState(true); } return counterBalancedCg; } @@ -4068,34 +4057,29 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl const float CYLINDER_TOP = 0.1f; const float CYLINDER_BOTTOM = -1.5f; const float SITTING_BOTTOM = -0.02f; + const float STANDING_HEIGHT_MULTIPLE = 1.2f; + const float SITTING_HEIGHT_MULTIPLE = 0.833f; + const float COSINE_TEN_DEGREES = 0.98f; + const int SITTING_COUNT_THRESHOLD = 300; + const int SQUATTY_COUNT_THRESHOLD = 600; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); auto sensorHeadPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); - glm::vec3 headWorldSpace = myAvatar.getHead()->getPosition(); - glm::mat4 worldToSensorMatrix = glm::inverse(myAvatar.getSensorToWorldMatrix()); - glm::vec3 headSensorSpace = transformVectorFast(myAvatar.getSensorToWorldMatrix(), headWorldSpace); - //get the mode. - //put it in sensor space. - // if we are 20% higher switch to standing. - // 16.6% lower then switch to sitting. - // add this !!!! And the head is upright. glm::vec3 upHead = transformVectorFast(sensorHeadPose.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); + glm::vec3 avatarHips = myAvatar.getAbsoluteJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); glm::vec3 worldHips = transformVectorFast(myAvatar.getTransform().getMatrix(),avatarHips); glm::vec3 sensorHips = transformVectorFast(myAvatar.getSensorToWorldMatrix(), worldHips); + float averageSensorSpaceHeight = myAvatar._sumUserHeightSensorSpace / myAvatar._averageUserHeightCount; - // we could add a counting here to make sure that a lean forward doesn't accidentally put you in sitting mode. - // but maybe so what. - // the real test is... can I pick something up in standing mode? - if (myAvatar.getIsInSittingState()) { if (offset.y < SITTING_BOTTOM) { // we recenter when sitting. return true; - } else if (sensorHeadPose.getTranslation().y > (1.2f * averageSensorSpaceHeight)) { + } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * averageSensorSpaceHeight)) { // if we recenter upwards then no longer in sitting state myAvatar.setIsInSittingState(false); return true; @@ -4104,16 +4088,18 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl } } else { // in the standing state - if ((sensorHeadPose.getTranslation().y < (0.83f * averageSensorSpaceHeight)) && (acosHead > 0.98f) && !(sensorHips.y > (0.4f * averageSensorSpaceHeight))) { + if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * myAvatar._tippingPoint)) && (acosHead > COSINE_TEN_DEGREES)) { //&& !(sensorHips.y > (0.4f * averageSensorSpaceHeight) myAvatar._sitStandStateCount++; - if (myAvatar._sitStandStateCount > 300) { + if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { myAvatar.setIsInSittingState(true); + myAvatar._tippingPoint = averageSensorSpaceHeight; myAvatar._sitStandStateCount = 0; myAvatar._squatCount = 0; return true; } } else { - if (myAvatar._squatCount > 600) { + myAvatar._tippingPoint = averageSensorSpaceHeight; + if (myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) { return true; myAvatar._squatCount = 0; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c7f6fa89ae..6fe5aeda47 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -140,6 +140,7 @@ class MyAvatar : public Avatar { * @property {number} walkSpeed * @property {number} walkBackwardSpeed * @property {number} sprintSpeed + * @property {number} isInSittingState * * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the * registration point of the 3D model. @@ -1818,6 +1819,7 @@ private: ThreadSafeValueCache _isInSittingState { false }; int _sitStandStateCount { 0 }; int _squatCount { 0 }; + float _tippingPoint { DEFAULT_AVATAR_HEIGHT }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; From 8a0fbc3fe9049e3322fd88e42024d698ae344d18 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 2 Oct 2018 17:47:56 -0700 Subject: [PATCH 047/276] changed the height to use the mode . to do fix sensor space bug. --- interface/src/avatar/MyAvatar.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4f28ae8e90..4892f98da1 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4074,12 +4074,18 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl glm::vec3 sensorHips = transformVectorFast(myAvatar.getSensorToWorldMatrix(), worldHips); float averageSensorSpaceHeight = myAvatar._sumUserHeightSensorSpace / myAvatar._averageUserHeightCount; + glm::vec3 modeWorldSpace = myAvatar.getTransform().getRotation() * glm::vec3(0.0f,myAvatar.getCurrentStandingHeight(),0.0f); + glm::vec3 modeSensorSpace = extractRotation(myAvatar.getSensorToWorldMatrix()) * modeWorldSpace; + glm::vec3 bodyInSensorSpace = transformPoint(glm::inverse(myAvatar.getSensorToWorldMatrix()), myAvatar.getTransform().getTranslation()); + modeSensorSpace.y = modeSensorSpace.y + bodyInSensorSpace.y; + qCDebug(interfaceapp) << "mode world sensor " << myAvatar.getCurrentStandingHeight() << " " << modeWorldSpace << " " << modeSensorSpace << " " << bodyInSensorSpace; if (myAvatar.getIsInSittingState()) { if (offset.y < SITTING_BOTTOM) { // we recenter when sitting. return true; - } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * averageSensorSpaceHeight)) { + // } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * averageSensorSpaceHeight)) { + } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * modeSensorSpace.y)) { // if we recenter upwards then no longer in sitting state myAvatar.setIsInSittingState(false); return true; @@ -4088,10 +4094,12 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl } } else { // in the standing state - if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * myAvatar._tippingPoint)) && (acosHead > COSINE_TEN_DEGREES)) { //&& !(sensorHips.y > (0.4f * averageSensorSpaceHeight) + // if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * myAvatar._tippingPoint)) && (acosHead > COSINE_TEN_DEGREES)) { //&& !(sensorHips.y > (0.4f * averageSensorSpaceHeight) + if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * modeSensorSpace.y)) && (acosHead > COSINE_TEN_DEGREES)) { myAvatar._sitStandStateCount++; if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { myAvatar.setIsInSittingState(true); + myAvatar.setResetMode(true); myAvatar._tippingPoint = averageSensorSpaceHeight; myAvatar._sitStandStateCount = 0; myAvatar._squatCount = 0; From 809ca0e51266ee8b25edb2ceeb5ce4e8b5491cd0 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 3 Oct 2018 18:05:30 -0700 Subject: [PATCH 048/276] vive hmd input is implemented. not using mode --- interface/src/avatar/MyAvatar.cpp | 45 ++++++++++++++++++------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4892f98da1..9db07347c3 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3821,10 +3821,14 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { } void MyAvatar::setIsInSittingState(bool isSitting) { - _isInSittingState.set(isSitting); + _sitStandStateCount = 0; + _squatCount = 0; controller::Pose sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); _sumUserHeightSensorSpace = sensorHeadPoseDebug.getTranslation().y; + _tippingPoint = sensorHeadPoseDebug.getTranslation().y; _averageUserHeightCount = 1; + setResetMode(true); + _isInSittingState.set(isSitting); emit sittingEnabledChanged(isSitting); } @@ -4070,45 +4074,48 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); glm::vec3 avatarHips = myAvatar.getAbsoluteJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); - glm::vec3 worldHips = transformVectorFast(myAvatar.getTransform().getMatrix(),avatarHips); - glm::vec3 sensorHips = transformVectorFast(myAvatar.getSensorToWorldMatrix(), worldHips); + glm::vec3 worldHips = transformPoint(myAvatar.getTransform().getMatrix(),avatarHips); + glm::vec3 sensorHips = transformPoint(glm::inverse(myAvatar.getSensorToWorldMatrix()), worldHips); float averageSensorSpaceHeight = myAvatar._sumUserHeightSensorSpace / myAvatar._averageUserHeightCount; - glm::vec3 modeWorldSpace = myAvatar.getTransform().getRotation() * glm::vec3(0.0f,myAvatar.getCurrentStandingHeight(),0.0f); - glm::vec3 modeSensorSpace = extractRotation(myAvatar.getSensorToWorldMatrix()) * modeWorldSpace; - glm::vec3 bodyInSensorSpace = transformPoint(glm::inverse(myAvatar.getSensorToWorldMatrix()), myAvatar.getTransform().getTranslation()); - modeSensorSpace.y = modeSensorSpace.y + bodyInSensorSpace.y; - qCDebug(interfaceapp) << "mode world sensor " << myAvatar.getCurrentStandingHeight() << " " << modeWorldSpace << " " << modeSensorSpace << " " << bodyInSensorSpace; + glm::vec3 modeWorldSpace = transformPoint(myAvatar.getTransform().getMatrix(), glm::vec3(0.0f,myAvatar.getCurrentStandingHeight(),0.0f)); + glm::vec3 modeSensorSpace = transformPoint(glm::inverse(myAvatar.getSensorToWorldMatrix()), modeWorldSpace); + //qCDebug(interfaceapp) << "mode reading avatar " << myAvatar.getCurrentStandingHeight() << "mode w " << modeWorldSpace << "mode s " << modeSensorSpace << "sensor pose " < (STANDING_HEIGHT_MULTIPLE * averageSensorSpaceHeight)) { - } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * modeSensorSpace.y)) { + } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * averageSensorSpaceHeight)) { + // } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * modeSensorSpace.y)) { // if we recenter upwards then no longer in sitting state - myAvatar.setIsInSittingState(false); - return true; + //myAvatar._sitStandStateCount++; + //if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { + myAvatar.setIsInSittingState(false); + return true; + //} } else { + myAvatar._sitStandStateCount = 0; + myAvatar._tippingPoint = averageSensorSpaceHeight; return false; } } else { // in the standing state // if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * myAvatar._tippingPoint)) && (acosHead > COSINE_TEN_DEGREES)) { //&& !(sensorHips.y > (0.4f * averageSensorSpaceHeight) - if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * modeSensorSpace.y)) && (acosHead > COSINE_TEN_DEGREES)) { + if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * myAvatar._tippingPoint))) { myAvatar._sitStandStateCount++; if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { myAvatar.setIsInSittingState(true); - myAvatar.setResetMode(true); - myAvatar._tippingPoint = averageSensorSpaceHeight; - myAvatar._sitStandStateCount = 0; - myAvatar._squatCount = 0; return true; } } else { myAvatar._tippingPoint = averageSensorSpaceHeight; + myAvatar._sitStandStateCount = 0; if (myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) { - return true; + // return true; myAvatar._squatCount = 0; } } @@ -4145,7 +4152,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Vertical); + //activate(Vertical); } } else { if (!isActive(Rotation) && getForceActivateRotation()) { From acce675efc4280740508d8e3562e6d39ea85bf40 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 4 Oct 2018 14:53:51 -0700 Subject: [PATCH 049/276] added snap for recentering with sit stand state change. using average height with tipping point for thresholds --- interface/src/avatar/MyAvatar.cpp | 41 ++++++++++++++++++++----------- interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9db07347c3..55536748c7 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4081,26 +4081,26 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl glm::vec3 modeWorldSpace = transformPoint(myAvatar.getTransform().getMatrix(), glm::vec3(0.0f,myAvatar.getCurrentStandingHeight(),0.0f)); glm::vec3 modeSensorSpace = transformPoint(glm::inverse(myAvatar.getSensorToWorldMatrix()), modeWorldSpace); - //qCDebug(interfaceapp) << "mode reading avatar " << myAvatar.getCurrentStandingHeight() << "mode w " << modeWorldSpace << "mode s " << modeSensorSpace << "sensor pose " < (STANDING_HEIGHT_MULTIPLE * averageSensorSpaceHeight)) { + //returnValue = true; + } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * myAvatar._tippingPoint)) { // } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * modeSensorSpace.y)) { // if we recenter upwards then no longer in sitting state - //myAvatar._sitStandStateCount++; - //if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { + myAvatar._sitStandStateCount++; + if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { myAvatar.setIsInSittingState(false); - return true; - //} + myAvatar._sitStandStateChange = true; + returnValue = true; + } } else { myAvatar._sitStandStateCount = 0; myAvatar._tippingPoint = averageSensorSpaceHeight; - return false; } } else { // in the standing state @@ -4109,7 +4109,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl myAvatar._sitStandStateCount++; if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { myAvatar.setIsInSittingState(true); - return true; + myAvatar._sitStandStateChange = true; + returnValue = true; } } else { myAvatar._tippingPoint = averageSensorSpaceHeight; @@ -4119,8 +4120,9 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl myAvatar._squatCount = 0; } } - return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + // returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); } + return returnValue; } void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, @@ -4152,7 +4154,9 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - //activate(Vertical); + activate(Vertical); + _timeRemaining[(int)Vertical] = 0.1f; + qCDebug(interfaceapp) << "recenter vertically!!!!!! " << hasDriveInput; } } else { if (!isActive(Rotation) && getForceActivateRotation()) { @@ -4209,6 +4213,9 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co // apply follow displacement to the body matrix. glm::vec3 worldLinearDisplacement = myAvatar.getCharacterController()->getFollowLinearDisplacement(); + if (worldLinearDisplacement.y > 0.0001f) { + qCDebug(interfaceapp) << "linear displacement " << worldLinearDisplacement << " time remaining " << getMaxTimeRemaining(); + } glm::quat worldAngularDisplacement = myAvatar.getCharacterController()->getFollowAngularDisplacement(); glm::mat4 sensorToWorldMatrix = myAvatar.getSensorToWorldMatrix(); @@ -4219,6 +4226,12 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix), sensorLinearDisplacement + extractTranslation(currentBodyMatrix)); + if (isActive(Vertical)) { + deactivate(Vertical); + return myAvatar.deriveBodyFromHMDSensor(); + } else { + return newBodyMat; + } return newBodyMat; } else { return currentBodyMatrix; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6fe5aeda47..7854f5cb41 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1807,6 +1807,7 @@ private: ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; float _sumUserHeightSensorSpace { DEFAULT_AVATAR_HEIGHT }; int _averageUserHeightCount { 1 }; + bool _sitStandStateChange { false }; void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); From 82f8c6743678527e6c14ae8b13b66038f2bc6143 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 4 Oct 2018 15:37:01 -0700 Subject: [PATCH 050/276] put the mode in sensor space for cg --- interface/src/avatar/MyAvatar.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 55536748c7..db37b5003b 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -493,7 +493,7 @@ void MyAvatar::update(float deltaTime) { _smoothOrientationTimer += deltaTime; } - float newHeightReading = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y; + float newHeightReading = getControllerPoseInSensorFrame(controller::Action::HEAD).getTranslation().y; int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER); _recentModeReadings.insert(newHeightReadingInCentimeters); setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD))); @@ -4015,6 +4015,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); + controller::Pose currentHeadSensorPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); bool stepDetected = false; float myScale = myAvatar.getAvatarScale(); @@ -4028,7 +4029,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons } else { if (!withinBaseOfSupport(currentHeadPose) && headAngularVelocityBelowThreshold(currentHeadPose) && - isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight(), myScale) && + isWithinThresholdHeightMode(currentHeadSensorPose, myAvatar.getCurrentStandingHeight(), myScale) && handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && headVelocityGreaterThanThreshold(currentHeadPose) && @@ -4083,7 +4084,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl // qCDebug(interfaceapp) << "mode reading avatar " << myAvatar.getCurrentStandingHeight() << "mode w " << modeWorldSpace << "mode s " << modeSensorSpace << "sensor pose " < Date: Thu, 4 Oct 2018 16:40:44 -0700 Subject: [PATCH 051/276] Restore missing abs() to quaternion dot-product See https://github.com/highfidelity/hifi/pull/14138 --- 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 07751d03e3..57d36e1f0f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -636,7 +636,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent // 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)) { + || (cullSmallChanges && fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT)) { validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE); #ifdef WANT_DEBUG rotationSentCount++; From 635300c6d5a7f8b686bf100ef39577af56650c12 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 4 Oct 2018 18:05:05 -0700 Subject: [PATCH 052/276] changed pre physics to use cg for desired body when in cg mode --- interface/src/avatar/MyAvatar.cpp | 58 +++++++++++++------------------ interface/src/avatar/MyAvatar.h | 4 +-- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index db37b5003b..09896e66c3 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -493,17 +493,14 @@ void MyAvatar::update(float deltaTime) { _smoothOrientationTimer += deltaTime; } - float newHeightReading = getControllerPoseInSensorFrame(controller::Action::HEAD).getTranslation().y; - int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER); - _recentModeReadings.insert(newHeightReadingInCentimeters); - setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD))); - setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); - - // if the head tracker reading is valid then add it to the running average. - auto sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); - if (sensorHeadPoseDebug.isValid()) { - _sumUserHeightSensorSpace += sensorHeadPoseDebug.getTranslation().y; + controller::Pose newHeightReading = getControllerPoseInSensorFrame(controller::Action::HEAD); + if (newHeightReading.isValid()) { + int newHeightReadingInCentimeters = glm::floor(newHeightReading.getTranslation().y * CENTIMETERS_PER_METER); + _sumUserHeightSensorSpace += newHeightReading.getTranslation().y; _averageUserHeightCount++; + _recentModeReadings.insert(newHeightReadingInCentimeters); + setCurrentStandingHeight(computeStandingHeightMode(newHeightReading)); + setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); } // if the spine is straight and the head is below the default position by 5 cm then increment squatty count. @@ -516,7 +513,7 @@ void MyAvatar::update(float deltaTime) { upSpine2 = glm::normalize(upSpine2); } float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); - if (newHeightReading < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && (angleSpine2 > COSINE_TEN_DEGREES)) { + if (getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && (angleSpine2 > COSINE_TEN_DEGREES)) { _squatCount++; } else { _squatCount = 0; @@ -2031,6 +2028,11 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setPositionAndOrientation(getWorldPosition(), getWorldOrientation()); auto headPose = getControllerPoseInAvatarFrame(controller::Action::HEAD); if (headPose.isValid()) { + if (getCenterOfGravityModelEnabled() && !getIsInSittingState()) { + _follow.prePhysicsUpdate(*this, deriveBodyUsingCgModel(), _bodySensorMatrix, hasDriveInput()); + } else { + _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); + } _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); @@ -3823,9 +3825,11 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { void MyAvatar::setIsInSittingState(bool isSitting) { _sitStandStateCount = 0; _squatCount = 0; - controller::Pose sensorHeadPoseDebug = getControllerPoseInSensorFrame(controller::Action::HEAD); - _sumUserHeightSensorSpace = sensorHeadPoseDebug.getTranslation().y; - _tippingPoint = sensorHeadPoseDebug.getTranslation().y; + controller::Pose sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); + if (sensorHeadPose.isValid()) { + _sumUserHeightSensorSpace = sensorHeadPose.getTranslation().y; + _tippingPoint = sensorHeadPose.getTranslation().y; + } _averageUserHeightCount = 1; setResetMode(true); _isInSittingState.set(isSitting); @@ -4077,21 +4081,14 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl glm::vec3 avatarHips = myAvatar.getAbsoluteJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); glm::vec3 worldHips = transformPoint(myAvatar.getTransform().getMatrix(),avatarHips); glm::vec3 sensorHips = transformPoint(glm::inverse(myAvatar.getSensorToWorldMatrix()), worldHips); - + //qCDebug(interfaceapp) << " current mode " << myAvatar.getCurrentStandingHeight() << " " << sensorHeadPose.getTranslation().y << " state " << myAvatar.getIsInSittingState(); float averageSensorSpaceHeight = myAvatar._sumUserHeightSensorSpace / myAvatar._averageUserHeightCount; - glm::vec3 modeWorldSpace = transformPoint(myAvatar.getTransform().getMatrix(), glm::vec3(0.0f,myAvatar.getCurrentStandingHeight(),0.0f)); - glm::vec3 modeSensorSpace = transformPoint(glm::inverse(myAvatar.getSensorToWorldMatrix()), modeWorldSpace); - - // qCDebug(interfaceapp) << "mode reading avatar " << myAvatar.getCurrentStandingHeight() << "mode w " << modeWorldSpace << "mode s " << modeSensorSpace << "sensor pose " < (STANDING_HEIGHT_MULTIPLE * myAvatar._tippingPoint)) { - // } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * modeSensorSpace.y)) { // if we recenter upwards then no longer in sitting state myAvatar._sitStandStateCount++; if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { @@ -4105,7 +4102,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl } } else { // in the standing state - // if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * myAvatar._tippingPoint)) && (acosHead > COSINE_TEN_DEGREES)) { //&& !(sensorHips.y > (0.4f * averageSensorSpaceHeight) + // && (acosHead > COSINE_TEN_DEGREES)) { //&& !(sensorHips.y > (0.4f * averageSensorSpaceHeight) if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * myAvatar._tippingPoint))) { myAvatar._sitStandStateCount++; if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { @@ -4114,14 +4111,14 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl returnValue = true; } } else { - myAvatar._tippingPoint = averageSensorSpaceHeight; + myAvatar._tippingPoint = myAvatar.getCurrentStandingHeight(); myAvatar._sitStandStateCount = 0; if (myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) { // return true; myAvatar._squatCount = 0; } } - // returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); } return returnValue; } @@ -4156,7 +4153,6 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); - _timeRemaining[(int)Vertical] = 0.1f; qCDebug(interfaceapp) << "recenter vertically!!!!!! " << hasDriveInput; } } else { @@ -4214,9 +4210,6 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co // apply follow displacement to the body matrix. glm::vec3 worldLinearDisplacement = myAvatar.getCharacterController()->getFollowLinearDisplacement(); - if (worldLinearDisplacement.y > 0.0001f) { - qCDebug(interfaceapp) << "linear displacement " << worldLinearDisplacement << " time remaining " << getMaxTimeRemaining(); - } glm::quat worldAngularDisplacement = myAvatar.getCharacterController()->getFollowAngularDisplacement(); glm::mat4 sensorToWorldMatrix = myAvatar.getSensorToWorldMatrix(); @@ -4228,10 +4221,9 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix), sensorLinearDisplacement + extractTranslation(currentBodyMatrix)); if (isActive(Vertical)) { + // myAvatar._sitStandStateChange = false; deactivate(Vertical); - return myAvatar.deriveBodyFromHMDSensor(); - } else { - return newBodyMat; + newBodyMat = myAvatar.deriveBodyFromHMDSensor(); } return newBodyMat; } else { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 7854f5cb41..0c6b1b01a5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1805,7 +1805,7 @@ private: // height of user in sensor space, when standing erect. ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; - float _sumUserHeightSensorSpace { DEFAULT_AVATAR_HEIGHT }; + float _sumUserHeightSensorSpace { 0.0f }; int _averageUserHeightCount { 1 }; bool _sitStandStateChange { false }; @@ -1820,7 +1820,7 @@ private: ThreadSafeValueCache _isInSittingState { false }; int _sitStandStateCount { 0 }; int _squatCount { 0 }; - float _tippingPoint { DEFAULT_AVATAR_HEIGHT }; + float _tippingPoint { 0.0f }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; From cbe638bfdb18d1420dacc470d46671ee4bb8c79e Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 4 Oct 2018 18:16:39 -0700 Subject: [PATCH 053/276] added flag for vertical recentering that is state change, snap, versus the usual .5 second recentering --- interface/src/avatar/MyAvatar.cpp | 6 +++--- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 09896e66c3..8756f1bbd8 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4203,7 +4203,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat myAvatar.getCharacterController()->setFollowParameters(followWorldPose, getMaxTimeRemaining()); } -glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) { +glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) { if (isActive()) { float dt = myAvatar.getCharacterController()->getFollowTime(); decrementTimeRemaining(dt); @@ -4220,8 +4220,8 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix), sensorLinearDisplacement + extractTranslation(currentBodyMatrix)); - if (isActive(Vertical)) { - // myAvatar._sitStandStateChange = false; + if (myAvatar._sitStandStateChange) { + myAvatar._sitStandStateChange = false; deactivate(Vertical); newBodyMat = myAvatar.deriveBodyFromHMDSensor(); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0c6b1b01a5..072ea04ee7 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1725,7 +1725,7 @@ private: bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); - glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); + glm::mat4 postPhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); bool getForceActivateRotation() const; void setForceActivateRotation(bool val); bool getForceActivateVertical() const; From ad46b7196634e301f9fdd5d5f8d0e11342959446 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 5 Oct 2018 13:04:14 -0700 Subject: [PATCH 054/276] before moving the step state counting to myavatar::update() --- interface/src/avatar/MyAvatar.cpp | 28 ++++++++++++++++++++++------ interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8756f1bbd8..c5defe3d72 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4103,6 +4103,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl } else { // in the standing state // && (acosHead > COSINE_TEN_DEGREES)) { //&& !(sensorHips.y > (0.4f * averageSensorSpaceHeight) + if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * myAvatar._tippingPoint))) { myAvatar._sitStandStateCount++; if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { @@ -4111,14 +4112,15 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl returnValue = true; } } else { + returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); myAvatar._tippingPoint = myAvatar.getCurrentStandingHeight(); myAvatar._sitStandStateCount = 0; if (myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) { - // return true; + // returnValue = true; myAvatar._squatCount = 0; } } - returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + } return returnValue; } @@ -4151,10 +4153,20 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } } - if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Vertical); - qCDebug(interfaceapp) << "recenter vertically!!!!!! " << hasDriveInput; + + qCDebug(interfaceapp) << "velocity of headset " << glm::length(myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD).getVelocity()); + + if (_velocityCount > 60) { + if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { + activate(Vertical); + qCDebug(interfaceapp) << "recenter vertically!!!!!! " << hasDriveInput; + } + } else { + if ((glm::length(myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD).getVelocity()) > 0.1f)) { + _velocityCount++; + } } + } else { if (!isActive(Rotation) && getForceActivateRotation()) { activate(Rotation); @@ -4223,7 +4235,11 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const gl if (myAvatar._sitStandStateChange) { myAvatar._sitStandStateChange = false; deactivate(Vertical); - newBodyMat = myAvatar.deriveBodyFromHMDSensor(); + + qCDebug(interfaceapp) << "before snap " << extractTranslation(newBodyMat); + //newBodyMat = myAvatar.deriveBodyFromHMDSensor(); + setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor())); + qCDebug(interfaceapp) << "after snap " << extractTranslation(newBodyMat); } return newBodyMat; } else { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 072ea04ee7..24a58745a3 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1738,6 +1738,7 @@ private: std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; std::atomic _toggleHipsFollowing { true }; + int _velocityCount { 0 }; }; FollowHelper _follow; From 96872f841202f4a60fc8b3b2a77f9632d0197ce8 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 5 Oct 2018 14:55:32 -0700 Subject: [PATCH 055/276] moved all the state update for sit and stand to myavatar::update() also made it so that sit horizontal reset is handled in non-cg recentering. to do: revisit what should be sent for desired body in prephysics update call --- interface/src/avatar/MyAvatar.cpp | 143 ++++++++++++++++++------------ 1 file changed, 87 insertions(+), 56 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c5defe3d72..58a0ef4046 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -467,6 +467,11 @@ void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders + const float STANDING_HEIGHT_MULTIPLE = 1.2f; + const float SITTING_HEIGHT_MULTIPLE = 0.833f; + const float COSINE_TEN_DEGREES = 0.98f; + const int SITTING_COUNT_THRESHOLD = 300; + const int SQUATTY_COUNT_THRESHOLD = 600; float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); @@ -505,7 +510,6 @@ void MyAvatar::update(float deltaTime) { // if the spine is straight and the head is below the default position by 5 cm then increment squatty count. const float SQUAT_THRESHOLD = 0.05f; - const float COSINE_TEN_DEGREES = 0.98f; glm::vec3 headDefaultPositionAvatarSpace = getAbsoluteDefaultJointTranslationInObjectFrame(getJointIndex("Head")); glm::quat spine2OrientationAvatarSpace = getAbsoluteJointRotationInObjectFrame(getJointIndex("Spine2")); glm::vec3 upSpine2 = spine2OrientationAvatarSpace * glm::vec3(0.0f, 1.0f, 0.0f); @@ -519,6 +523,65 @@ void MyAvatar::update(float deltaTime) { _squatCount = 0; } + float averageSensorSpaceHeight = _sumUserHeightSensorSpace / _averageUserHeightCount; + //auto sensorHeadPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); + //glm::vec3 upHead = transformVectorFast(sensorHeadPose.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); + //float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); + + glm::vec3 avatarHips = getAbsoluteJointTranslationInObjectFrame(getJointIndex("Hips")); + glm::vec3 worldHips = transformPoint(getTransform().getMatrix(), avatarHips); + glm::vec3 sensorHips = transformPoint(glm::inverse(getSensorToWorldMatrix()), worldHips); + + // put update sit stand state counts here + if (getIsInSittingState()) { + if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we recenter upwards then no longer in sitting state + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setResetMode(true); + setIsInSittingState(false); + setCenterOfGravityModelEnabled(true); + _sitStandStateChange = true; + } + } else { + _sitStandStateCount = 0; + // tipping point is average height when sitting. + _tippingPoint = averageSensorSpaceHeight; + } + } else { + // in the standing state + if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint))) {// && (angleSpine2 > COSINE_TEN_DEGREES) && !(sensorHips.y > (0.4f * averageSensorSpaceHeight))) { + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setResetMode(true); + setIsInSittingState(true); + setCenterOfGravityModelEnabled(false); + _sitStandStateChange = true; + } + } else { + // returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + // use the mode height for the tipping point when we are standing. + _tippingPoint = getCurrentStandingHeight(); + _sitStandStateCount = 0; + } + + } + + if (_drawAverageFacingEnabled) { auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation()); @@ -2028,10 +2091,10 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setPositionAndOrientation(getWorldPosition(), getWorldOrientation()); auto headPose = getControllerPoseInAvatarFrame(controller::Action::HEAD); if (headPose.isValid()) { - if (getCenterOfGravityModelEnabled() && !getIsInSittingState()) { - _follow.prePhysicsUpdate(*this, deriveBodyUsingCgModel(), _bodySensorMatrix, hasDriveInput()); + if (getCenterOfGravityModelEnabled()) { + //_follow.prePhysicsUpdate(*this, deriveBodyUsingCgModel(), _bodySensorMatrix, hasDriveInput()); } else { - _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); + //_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { @@ -3995,6 +4058,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, // x axis of currentBodyMatrix in world space. glm::vec3 right = glm::normalize(glm::vec3(currentBodyMatrix[0][0], currentBodyMatrix[1][0], currentBodyMatrix[2][0])); glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); float forwardLeanAmount = glm::dot(forward, offset); float lateralLeanAmount = glm::dot(right, offset); @@ -4003,14 +4067,19 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const float MAX_FORWARD_LEAN = 0.15f; const float MAX_BACKWARD_LEAN = 0.1f; - - if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) { - return true; + bool stepDetected = false; + if (myAvatar.getIsInSittingState()) { + if (!withinBaseOfSupport(currentHeadPose)) { + stepDetected = true; + } + } else if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) { + stepDetected = true; } else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) { - return true; + stepDetected = true; + } else { + stepDetected = fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; } - - return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; + return stepDetected; } bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { @@ -4026,10 +4095,6 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons if (myAvatar.getIsInWalkingState()) { stepDetected = true; - } else if (myAvatar.getIsInSittingState()) { - if (!withinBaseOfSupport(currentHeadPose)) { - stepDetected = true; - } } else { if (!withinBaseOfSupport(currentHeadPose) && headAngularVelocityBelowThreshold(currentHeadPose) && @@ -4066,61 +4131,27 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl const float CYLINDER_TOP = 0.1f; const float CYLINDER_BOTTOM = -1.5f; const float SITTING_BOTTOM = -0.02f; - const float STANDING_HEIGHT_MULTIPLE = 1.2f; - const float SITTING_HEIGHT_MULTIPLE = 0.833f; - const float COSINE_TEN_DEGREES = 0.98f; - const int SITTING_COUNT_THRESHOLD = 300; const int SQUATTY_COUNT_THRESHOLD = 600; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); - - auto sensorHeadPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); - glm::vec3 upHead = transformVectorFast(sensorHeadPose.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); - float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); - - glm::vec3 avatarHips = myAvatar.getAbsoluteJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); - glm::vec3 worldHips = transformPoint(myAvatar.getTransform().getMatrix(),avatarHips); - glm::vec3 sensorHips = transformPoint(glm::inverse(myAvatar.getSensorToWorldMatrix()), worldHips); //qCDebug(interfaceapp) << " current mode " << myAvatar.getCurrentStandingHeight() << " " << sensorHeadPose.getTranslation().y << " state " << myAvatar.getIsInSittingState(); - float averageSensorSpaceHeight = myAvatar._sumUserHeightSensorSpace / myAvatar._averageUserHeightCount; + bool returnValue = false; + if (myAvatar._sitStandStateChange) { + returnValue = true; + } if (myAvatar.getIsInSittingState()) { if (offset.y < SITTING_BOTTOM) { // we recenter when sitting. returnValue = true; - } else if (sensorHeadPose.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * myAvatar._tippingPoint)) { - // if we recenter upwards then no longer in sitting state - myAvatar._sitStandStateCount++; - if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { - myAvatar.setIsInSittingState(false); - myAvatar._sitStandStateChange = true; - returnValue = true; - } - } else { - myAvatar._sitStandStateCount = 0; - myAvatar._tippingPoint = averageSensorSpaceHeight; } } else { // in the standing state - // && (acosHead > COSINE_TEN_DEGREES)) { //&& !(sensorHips.y > (0.4f * averageSensorSpaceHeight) - - if ((sensorHeadPose.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * myAvatar._tippingPoint))) { - myAvatar._sitStandStateCount++; - if (myAvatar._sitStandStateCount > SITTING_COUNT_THRESHOLD) { - myAvatar.setIsInSittingState(true); - myAvatar._sitStandStateChange = true; - returnValue = true; - } - } else { - returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); - myAvatar._tippingPoint = myAvatar.getCurrentStandingHeight(); - myAvatar._sitStandStateCount = 0; - if (myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) { - // returnValue = true; - myAvatar._squatCount = 0; - } + returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + if (myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) { + myAvatar._squatCount = 0; + returnValue = true; } - } return returnValue; } From 3517905435d5b0fa2c276f9c313de8de5460712e Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 5 Oct 2018 16:22:30 -0700 Subject: [PATCH 056/276] added getter and setter for sitstandstatechange bool --- interface/src/avatar/MyAvatar.cpp | 50 ++++++++++--------------------- interface/src/avatar/MyAvatar.h | 18 ++++++----- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 58a0ef4046..995126f3ca 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -524,14 +524,11 @@ void MyAvatar::update(float deltaTime) { } float averageSensorSpaceHeight = _sumUserHeightSensorSpace / _averageUserHeightCount; - //auto sensorHeadPose = myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD); - //glm::vec3 upHead = transformVectorFast(sensorHeadPose.getMatrix(), glm::vec3(0.0f, 1.0f, 0.0f)); - //float acosHead = glm::dot(upHead, glm::vec3(0.0f, 1.0f, 0.0f)); glm::vec3 avatarHips = getAbsoluteJointTranslationInObjectFrame(getJointIndex("Hips")); glm::vec3 worldHips = transformPoint(getTransform().getMatrix(), avatarHips); glm::vec3 sensorHips = transformPoint(glm::inverse(getSensorToWorldMatrix()), worldHips); - + // put update sit stand state counts here if (getIsInSittingState()) { if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { @@ -548,11 +545,11 @@ void MyAvatar::update(float deltaTime) { setResetMode(true); setIsInSittingState(false); setCenterOfGravityModelEnabled(true); - _sitStandStateChange = true; + setSitStandStateChange(true); } } else { _sitStandStateCount = 0; - // tipping point is average height when sitting. + // tipping point is average height when sitting. _tippingPoint = averageSensorSpaceHeight; } } else { @@ -570,7 +567,7 @@ void MyAvatar::update(float deltaTime) { setResetMode(true); setIsInSittingState(true); setCenterOfGravityModelEnabled(false); - _sitStandStateChange = true; + setSitStandStateChange(true); } } else { // returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); @@ -2091,11 +2088,6 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setPositionAndOrientation(getWorldPosition(), getWorldOrientation()); auto headPose = getControllerPoseInAvatarFrame(controller::Action::HEAD); if (headPose.isValid()) { - if (getCenterOfGravityModelEnabled()) { - //_follow.prePhysicsUpdate(*this, deriveBodyUsingCgModel(), _bodySensorMatrix, hasDriveInput()); - } else { - //_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); - } _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); @@ -3886,15 +3878,6 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { } void MyAvatar::setIsInSittingState(bool isSitting) { - _sitStandStateCount = 0; - _squatCount = 0; - controller::Pose sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); - if (sensorHeadPose.isValid()) { - _sumUserHeightSensorSpace = sensorHeadPose.getTranslation().y; - _tippingPoint = sensorHeadPose.getTranslation().y; - } - _averageUserHeightCount = 1; - setResetMode(true); _isInSittingState.set(isSitting); emit sittingEnabledChanged(isSitting); } @@ -3915,6 +3898,14 @@ float MyAvatar::getSprintSpeed() const { return _sprintSpeed.get(); } +void MyAvatar::setSitStandStateChange(bool stateChanged) { + _sitStandStateChange = stateChanged; +} + +float MyAvatar::getSitStandStateChange() const { + return _sitStandStateChange; +} + QVector MyAvatar::getScriptUrls() { QVector scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector(); return scripts; @@ -4134,10 +4125,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl const int SQUATTY_COUNT_THRESHOLD = 600; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); - //qCDebug(interfaceapp) << " current mode " << myAvatar.getCurrentStandingHeight() << " " << sensorHeadPose.getTranslation().y << " state " << myAvatar.getIsInSittingState(); - bool returnValue = false; - if (myAvatar._sitStandStateChange) { + if (myAvatar.getSitStandStateChange()) { returnValue = true; } if (myAvatar.getIsInSittingState()) { @@ -4184,13 +4173,10 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } } - - qCDebug(interfaceapp) << "velocity of headset " << glm::length(myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD).getVelocity()); - + if (_velocityCount > 60) { if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); - qCDebug(interfaceapp) << "recenter vertically!!!!!! " << hasDriveInput; } } else { if ((glm::length(myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD).getVelocity()) > 0.1f)) { @@ -4263,14 +4249,10 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar, const gl glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix), sensorLinearDisplacement + extractTranslation(currentBodyMatrix)); - if (myAvatar._sitStandStateChange) { - myAvatar._sitStandStateChange = false; + if (myAvatar.getSitStandStateChange()) { + myAvatar.setSitStandStateChange(false); deactivate(Vertical); - - qCDebug(interfaceapp) << "before snap " << extractTranslation(newBodyMat); - //newBodyMat = myAvatar.deriveBodyFromHMDSensor(); setTranslation(newBodyMat, extractTranslation(myAvatar.deriveBodyFromHMDSensor())); - qCDebug(interfaceapp) << "after snap " << extractTranslation(newBodyMat); } return newBodyMat; } else { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 24a58745a3..54aa015aff 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1105,6 +1105,8 @@ public: float getWalkBackwardSpeed() const; void setSprintSpeed(float value); float getSprintSpeed() const; + void setSitStandStateChange(bool stateChanged); + float getSitStandStateChange() const; QVector getScriptUrls(); @@ -1804,14 +1806,16 @@ private: std::mutex _pinnedJointsMutex; std::vector _pinnedJoints; - // height of user in sensor space, when standing erect. - ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; - float _sumUserHeightSensorSpace { 0.0f }; - int _averageUserHeightCount { 1 }; - bool _sitStandStateChange { false }; - void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); + const float DEFAULT_FLOOR_HEIGHT = 0.0f; + + // height of user in sensor space, when standing erect. + ThreadSafeValueCache _userHeight{ DEFAULT_AVATAR_HEIGHT }; + float _sumUserHeightSensorSpace{ DEFAULT_AVATAR_HEIGHT }; + int _averageUserHeightCount{ 1 }; + bool _sitStandStateChange{ false }; + // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; ThreadSafeValueCache _walkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED }; @@ -1821,7 +1825,7 @@ private: ThreadSafeValueCache _isInSittingState { false }; int _sitStandStateCount { 0 }; int _squatCount { 0 }; - float _tippingPoint { 0.0f }; + float _tippingPoint { DEFAULT_FLOOR_HEIGHT }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; From ea8229fca49862635cf90e396815d795567fa46d Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 8 Oct 2018 13:10:41 -0700 Subject: [PATCH 057/276] adding fix for length check --- .../system/controllers/controllerModules/farActionGrabEntity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 2e73526728..2fe98ae673 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -317,7 +317,7 @@ Script.include("/~/system/libraries/Xform.js"); }; this.restoreIgnoredEntities = function() { - for (var i = 0; i < this.ignoredEntities; i++) { + for (var i = 0; i < this.ignoredEntities.length; i++) { var data = { action: 'remove', id: this.ignoredEntities[i] From 6f9593dabd4dc06b48faeb04f4d5d2869b21660a Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 8 Oct 2018 16:59:39 -0700 Subject: [PATCH 058/276] adding fix for far action grab --- .../controllerModules/farActionGrabEntity.js | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 2fe98ae673..4e9ce44d64 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -336,15 +336,6 @@ Script.include("/~/system/libraries/Xform.js"); if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { return true; - } else if (intersection.type === Picks.INTERSECTED_ENTITY && !Window.isPhysicsEnabled()) { - // add to ignored items. - var data = { - action: 'add', - id: intersection.objectID - }; - Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); - this.ignoredEntities.push(intersection.objectID); - } return false; }; @@ -405,7 +396,6 @@ Script.include("/~/system/libraries/Xform.js"); this.isReady = function (controllerData) { if (HMD.active) { if (this.notPointingAtEntity(controllerData)) { - this.restoreIgnoredEntities(); return makeRunningValues(false, [], []); } @@ -417,17 +407,28 @@ Script.include("/~/system/libraries/Xform.js"); return makeRunningValues(true, [], []); } else { this.destroyContextOverlay(); - this.restoreIgnoredEntities(); return makeRunningValues(false, [], []); } } - this.restoreIgnoredEntities(); return makeRunningValues(false, [], []); }; this.run = function (controllerData) { + + var intersection = controllerData.rayPicks[this.hand]; + if (intersection.type === Picks.INTERSECTED_ENTITY && !Window.isPhysicsEnabled()) { + // add to ignored items. + if (this.ignoredEntities.indexOf(intersection.objectID) === -1) { + var data = { + action: 'add', + id: intersection.objectID + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); + this.ignoredEntities.push(intersection.objectID); + } + } if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || - this.notPointingAtEntity(controllerData) || this.targetIsNull()) { + (this.notPointingAtEntity(controllerData) && Window.isPhysicsEnabled()) || this.targetIsNull()) { this.endFarGrabAction(); Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); From 27d9e8bd23a8cf00aa5b591e26f50bbfdfa73a39 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 9 Oct 2018 10:11:30 -0700 Subject: [PATCH 059/276] initial pass refactored re-ordered data-driver entity properties --- scripts/system/html/css/edit-style.css | 495 +-- scripts/system/html/entityProperties.html | 870 ----- scripts/system/html/js/entityProperties.js | 3781 +++++++++++--------- 3 files changed, 2043 insertions(+), 3103 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 6c1931932a..b7aac02c7c 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -448,10 +448,6 @@ input[type=checkbox]:checked + label:hover { border: 1.5pt solid black; } -.shape-section, .light-section, .model-section, .web-section, .image-section, .hyperlink-section, .text-section, .zone-section, .material-section { - display: table; -} - #properties-list fieldset { position: relative; /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ @@ -560,21 +556,6 @@ hr { padding-top: 2px; } -.text-group[collapsed="true"] ~ .text-group, -.zone-group[collapsed="true"] ~ .zone-group, -.image-group[collapsed="true"] ~ .image-group, -.web-group[collapsed="true"] ~ .web-group, -.hyperlink-group[collapsed="true"] ~ .hyperlink-group, -.spatial-group[collapsed="true"] ~ .spatial-group, -.physical-group[collapsed="true"] ~ .physical-group, -.behavior-group[collapsed="true"] ~ .behavior-group, -.model-group[collapsed="true"] ~ .model-group, -.material-group[collapsed="true"] ~ .material-group, -.light-group[collapsed="true"] ~ .light-group { - display: none !important; -} - - .property { display: table; width: 100%; @@ -633,10 +614,6 @@ hr { margin-top: 0; } -.checkbox-sub-props { - margin-top: 18px; -} - .property .number { float: left; } @@ -800,15 +777,6 @@ div.refresh input[type="button"] { display: none !important; } -#property-color-control1 { - display: table-cell; - float: none; -} - -#property-color-control1 + label { - border-left: 20px transparent solid; -} - .rgb label { float: left; margin-top: 10px; @@ -873,10 +841,10 @@ div.refresh input[type="button"] { font-family: FiraSans-SemiBold; font-size: 12px; } -.tuple .red + label, .tuple .x + label, .tuple .pitch + label { +.tuple .red + label, .tuple .x + label, .tuple .pitch + label, .tuple .width + label { color: #e2334d; } -.tuple .green + label, .tuple .y + label, .tuple .yaw + label { +.tuple .green + label, .tuple .y + label, .tuple .yaw + label, .tuple .height + label { color: #1ac567; } .tuple .blue + label, .tuple .z + label, .tuple .roll + label { @@ -950,14 +918,6 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { outline: none; } -fieldset .checkbox-sub-props { - margin-top: 0; - } - -fieldset .checkbox-sub-props .property:first-child { - margin-top: 0; -} - .column { vertical-align: top; } @@ -1298,51 +1258,48 @@ th#entity-hasScript { color: #afafaf; } - -#properties-list #properties-header { - display: table-row; - height: 28px; - border-top: none; - box-shadow: none; -} - -#properties-header .property { - display: table-cell; - vertical-align: middle; -} -#properties-header .checkbox { - position: relative; - top: -1px; -} - -#properties-header #type-icon { +#base #property-type-icon { font-family: hifi-glyphs; font-size: 31px; color: #00b4ef; margin: -4px 12px -4px -2px; width: auto; display: none; - vertical-align: middle; } -#properties-header #property-type { +#base #property-type { padding: 5px 24px 5px 0; - border-right: 1px solid #808080; - height: 100%; + border-right: 1px solid #808080; width: auto; display: inline-block; - vertical-align: middle; } -#properties-header .checkbox:last-child { +#base #div-locked { + position: absolute; + top: 0px; + right: 140px; +} + +#base #div-visible { + position: absolute; + top: 20px; + right: 20px; +} + +#base .checkbox { + position: relative; + top: -1px; +} + +#base .checkbox:last-child { padding-left: 24px; } -#properties-header .checkbox label { +#base .checkbox label { background-position-y: 1px; } -#properties-header .checkbox label span { +#base .checkbox label span { font-family: HiFi-Glyphs; font-size: 20px; padding-right: 6px; @@ -1351,11 +1308,11 @@ th#entity-hasScript { top: -4px; } -#properties-header input[type=checkbox]:checked + label span { +#base input[type=checkbox]:checked + label span { color: #ffffff; } -#properties-header + hr { +#base + hr { margin-top: 12px; } @@ -1371,30 +1328,21 @@ th#entity-hasScript { background-color: #00b4ef; } -input#property-parent-id { - width: 340px; -} - -input#dimension-rescale-button { +input#property-scale-button-rescale { min-width: 50px; margin-left: 6px; } -input#reset-to-natural-dimensions { +input#property-scale-button-reset { margin-right: 0; } -#animation-fps { - margin-top: 48px; -} - -#userdata-clear, -#materialdata-clear { +#property-userData-button-clear, +#property-materialData-button-clear { margin-bottom: 10px; } - -#static-userdata, -#static-materialData { +#property-userData-static, +#property-materialData-static { display: none; z-index: 99; position: absolute; @@ -1405,13 +1353,19 @@ input#reset-to-natural-dimensions { background-color: #2e2e2e; } -#userdata-saved, -#materialData-saved { +#property-userData-saved, +#property-materialData-saved { margin-top:5px; font-size:16px; display:none; } +#property-serverScripts-status { + position: relative; + top: -3px; + right: -20px; +} + #properties-list #collision-info > fieldset:first-of-type { border-top: none !important; box-shadow: none; @@ -1421,371 +1375,4 @@ input#reset-to-natural-dimensions { #properties-list { display: flex; flex-direction: column; -} - -/* ----- Order of Menu items for Primitive ----- */ -/* Entity Menu classes are specified by selected entity - within entityProperties.js -*/ -#properties-list.ShapeMenu #general, -#properties-list.BoxMenu #general, -#properties-list.SphereMenu #general { - order: 1; -} - -#properties-list.ShapeMenu #collision-info, -#properties-list.BoxMenu #collision-info, -#properties-list.SphereMenu #collision-info { - order: 2; -} - -#properties-list.ShapeMenu #physical, -#properties-list.BoxMenu #physical, -#properties-list.SphereMenu #physical { - order: 3; -} - -#properties-list.ShapeMenu #spatial, -#properties-list.BoxMenu #spatial, -#properties-list.SphereMenu #spatial { - order: 4; -} - -#properties-list.ShapeMenu #behavior, -#properties-list.BoxMenu #behavior, -#properties-list.SphereMenu #behavior { - order: 5; -} - -#properties-list.ShapeMenu #hyperlink, -#properties-list.BoxMenu #hyperlink, -#properties-list.SphereMenu #hyperlink { - order: 6; -} - -#properties-list.ShapeMenu #material, -#properties-list.BoxMenu #material, -#properties-list.SphereMenu #material, -#properties-list.ShapeMenu #light, -#properties-list.BoxMenu #light, -#properties-list.SphereMenu #light, -#properties-list.ShapeMenu #model, -#properties-list.BoxMenu #model, -#properties-list.SphereMenu #model, -#properties-list.ShapeMenu #zone, -#properties-list.BoxMenu #zone, -#properties-list.SphereMenu #zone, -#properties-list.ShapeMenu #text, -#properties-list.BoxMenu #text, -#properties-list.SphereMenu #text, -#properties-list.ShapeMenu #image, -#properties-list.BoxMenu #image, -#properties-list.SphereMenu #image, -#properties-list.ShapeMenu #web, -#properties-list.BoxMenu #web, -#properties-list.SphereMenu #web { - display: none; -} - -/* ----- ParticleEffectMenu ----- */ -#properties-list.ParticleEffectMenu #general { - order: 1; -} -#properties-list.ParticleEffectMenu #collision-info { - order: 2; -} -#properties-list.ParticleEffectMenu #physical { - order: 3; -} -#properties-list.ParticleEffectMenu #spatial { - order: 4; -} -#properties-list.ParticleEffectMenu #behavior { - order: 5; -} - -/* items to hide */ -#properties-list.ParticleEffectMenu #material, -#properties-list.ParticleEffectMenu #base-color-section, -#properties-list.ParticleEffectMenu #hyperlink, -#properties-list.ParticleEffectMenu #light, -#properties-list.ParticleEffectMenu #model, -#properties-list.ParticleEffectMenu #shape-list, -#properties-list.ParticleEffectMenu #text, -#properties-list.ParticleEffectMenu #web, -#properties-list.ParticleEffectMenu #image, -#properties-list.ParticleEffectMenu #zone { - display: none; -} - -/* ----- Order of Menu items for Light ----- */ -#properties-list.LightMenu #general { - order: 1; -} -#properties-list.LightMenu #light { - order: 2; -} -#properties-list.LightMenu #physical { - order: 3; -} -#properties-list.LightMenu #spatial { - order: 4; -} -#properties-list.LightMenu #behavior { - order: 5; -} -#properties-list.LightMenu #collision-info { - order: 6; -} -#properties-list.LightMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.LightMenu #material, -#properties-list.LightMenu #model, -#properties-list.LightMenu #zone, -#properties-list.LightMenu #text, -#properties-list.LightMenu #image, -#properties-list.LightMenu #web { - display: none; -} -/* items to hide */ -#properties-list.LightMenu #shape-list, -#properties-list.LightMenu #base-color-section { - display: none -} - - -/* ----- Order of Menu items for Model ----- */ -#properties-list.ModelMenu #general { - order: 1; -} -#properties-list.ModelMenu #model { - order: 2; -} -#properties-list.ModelMenu #collision-info { - order: 3; -} -#properties-list.ModelMenu #physical { - order: 4; -} -#properties-list.ModelMenu #spatial { - order: 5; -} -#properties-list.ModelMenu #behavior { - order: 6; -} -#properties-list.ModelMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.ModelMenu #material, -#properties-list.ModelMenu #light, -#properties-list.ModelMenu #zone, -#properties-list.ModelMenu #text, -#properties-list.ModelMenu #image, -#properties-list.ModelMenu #web { - display: none; -} -/* items to hide */ -#properties-list.ModelMenu #shape-list, -#properties-list.ModelMenu #base-color-section { - display: none -} - - -/* ----- Order of Menu items for Zone ----- */ -#properties-list.ZoneMenu #general { - order: 1; -} -#properties-list.ZoneMenu #zone { - order: 2; -} -#properties-list.ZoneMenu #physical { - order: 3; -} -#properties-list.ZoneMenu #spatial { - order: 4; -} -#properties-list.ZoneMenu #behavior { - order: 5; -} -#properties-list.ZoneMenu #collision-info { - order: 6; -} -#properties-list.ZoneMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.ZoneMenu #material, -#properties-list.ZoneMenu #light, -#properties-list.ZoneMenu #model, -#properties-list.ZoneMenu #text, -#properties-list.ZoneMenu #image, -#properties-list.ZoneMenu #web { - display: none; -} -/* items to hide */ -#properties-list.ZoneMenu #shape-list, -#properties-list.ZoneMenu #base-color-section { - display: none -} - - -/* ----- Order of Menu items for Image ----- */ -#properties-list.ImageMenu #general { - order: 1; -} -#properties-list.ImageMenu #image { - order: 2; -} -#properties-list.ImageMenu #collision-info { - order: 3; -} -#properties-list.ImageMenu #physical { - order: 4; -} -#properties-list.ImageMenu #spatial { - order: 5; -} -#properties-list.ImageMenu #behavior { - order: 6; -} -#properties-list.ImageMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.ImageMenu #material, -#properties-list.ImageMenu #light, -#properties-list.ImageMenu #model, -#properties-list.ImageMenu #zone, -#properties-list.ImageMenu #web, -#properties-list.ImageMenu #text { - display: none; -} -/* items to hide */ -#properties-list.ImageMenu #shape-list, -#properties-list.ImageMenu #base-color-section { - display: none; -} - - -/* ----- Order of Menu items for Web ----- */ -#properties-list.WebMenu #general { - order: 1; -} -#properties-list.WebMenu #web { - order: 2; -} -#properties-list.WebMenu #collision-info { - order: 3; -} -#properties-list.WebMenu #physical { - order: 4; -} -#properties-list.WebMenu #spatial { - order: 5; -} -#properties-list.WebMenu #behavior { - order: 6; -} -#properties-list.WebMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.WebMenu #material, -#properties-list.WebMenu #light, -#properties-list.WebMenu #model, -#properties-list.WebMenu #zone, -#properties-list.WebMenu #image, -#properties-list.WebMenu #text { - display: none; -} -/* items to hide */ -#properties-list.WebMenu #shape-list, -#properties-list.WebMenu #base-color-section { - display: none; -} - - - -/* ----- Order of Menu items for Text ----- */ -#properties-list.TextMenu #general { - order: 1; -} -#properties-list.TextMenu #text { - order: 2; -} -#properties-list.TextMenu #collision-info { - order: 3; -} -#properties-list.TextMenu #physical { - order: 4; -} -#properties-list.TextMenu #spatial { - order: 5; -} -#properties-list.TextMenu #behavior { - order: 6; -} -#properties-list.TextMenu #hyperlink { - order: 7; -} -/* sections to hide */ -#properties-list.TextMenu #material, -#properties-list.TextMenu #light, -#properties-list.TextMenu #model, -#properties-list.TextMenu #zone, -#properties-list.TextMenu #image, -#properties-list.TextMenu #web { - display: none; -} -/* items to hide */ -#properties-list.TextMenu #shape-list, -#properties-list.TextMenu #base-color-section { - display: none -} - -/* ----- Order of Menu items for Material ----- */ -#properties-list.MaterialMenu #general { - order: 1; -} -#properties-list.MaterialMenu #material { - order: 2; -} -#properties-list.MaterialMenu #spatial { - order: 3; -} -#properties-list.MaterialMenu #hyperlink { - order: 4; -} -#properties-list.MaterialMenu #behavior { - order: 5; -} - -/* sections to hide */ -#properties-list.MaterialMenu #physical, -#properties-list.MaterialMenu #collision-info, -#properties-list.MaterialMenu #model, -#properties-list.MaterialMenu #light, -#properties-list.MaterialMenu #zone, -#properties-list.MaterialMenu #text, -#properties-list.MaterialMenu #web, -#properties-list.MaterialMenu #image { - display: none; -} -/* items to hide */ -#properties-list.MaterialMenu #shape-list, -#properties-list.MaterialMenu #base-color-section { - display: none -} - - -/* Currently always hidden */ -#properties-list #polyvox { - display: none; -} - -.skybox-section { - display: none; } \ No newline at end of file diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 744150253d..93f80e19b3 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -26,876 +26,6 @@
- -
-
- -
-
- - -
-
- - -
-
- -
- -
- - -
-
-
- -
-
-
-
-
-
-
- -
- CollisionM -
-
- - -
-
- - -
-
-
-
-
- - Collides With - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- - Grabbing - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
-
- - -
- - Physical M - -
-
- Linear velocity m/s -
-
-
-
-
-
-
- - -
-
-
-
- Angular velocity deg/s -
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
- Gravity m/s2 -
-
-
-
-
-
-
- Acceleration m/s2 -
-
-
-
-
-
-
-
- - -
- - SpatialM - -
- Position m -
-
-
-
-
-
-
- Rotation deg -
-
-
-
-
-
-
- Dimensions m -
-
-
-
-
-
-
- Registration (pivot offset as ratio of dimension) -
-
-
-
-
-
-
- Scale % -
- - - -
-
-
-
- - -
-
- - -
-
-
-
- -
-
-
- - -
-
-
-
- -
- - BehaviorM - -
- -
-
- - - - Saved! -
-
-
- -
-
- - -
-
- - -
- -
-
- - -
-
- - -
-
-
-
- - - - -
-
-
-
- - - -
-
- - -
-
- -
-
-
- - -
-
- - - - -
- - LightM - -
-
- Light color -
-
-
-
-
-
-
-
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
- -
- - ModelM - -
-
- - -
- -
- - -
-
-
-
- - -
- -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
-
-
- - -
-
- - -
-
-
- -
- - ZoneM - -
-
- - -
-
- - -
-
-
-
- - -
-
- -
-
- Inherit - Off - On -
-
-
- -
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
- - -
-
- - Skybox - -
- Inherit - Off - On -
-
-
- Skybox color -
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
- -
-
-
- -
-
- Inherit - Off - On -
-
- - -
-
- - -
-
-
- - Haze - -
- Inherit - Off - On -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
-
-
-
- Haze Color -
-
-
-
-
-
-
-
-
-
-
-
-
- - - - -
-
-
-
-
- - -
-
-
- Glare Color -
-
-
-
-
-
-
-
-
-
-
-
-
- - - -
-
-
-
-
- -
-
- - Bloom - -
- Inherit - Off - On -
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
- - TextM - -
- - -
-
- - -
-
- - -
-
-
- -
-
-
-
-
-
-
-
- Background color -
-
-
-
-
-
-
- -
- - ImageM - -
- - -
-
- -
- - WebM - -
- - -
-
- - -
-
- -
- - Voxel volume size m - -
-
-
-
-
- -
- - -
-
- - -
-
- - -
-
- -
- - MaterialM - -
-
- - -
- -
- -

-
- - - - Saved! -
-
-
- -
-
-
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
-
-
-
- -
- -
-
-
-
- -
- - -
-
- -
-
-
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index de9027586e..03dad80be6 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1,4 +1,5 @@ // entityProperties.js +// entityProperties.js // // Created by Ryan Huffman on 13 Nov 2014 // Copyright 2014 High Fidelity, Inc. @@ -8,11 +9,8 @@ /* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, HifiEntityUI, JSONEditor, openEventBridge, setTimeout, window, _ $ */ - -var PI = 3.14159265358979; -var DEGREES_TO_RADIANS = PI / 180.0; -var RADIANS_TO_DEGREES = 180.0 / PI; -var ICON_FOR_TYPE = { + +const ICON_FOR_TYPE = { Box: "V", Sphere: "n", Shape: "n", @@ -27,17 +25,901 @@ var ICON_FOR_TYPE = { Multiple: "", PolyLine: "", Material: "" +}; + +const PI = 3.14159265358979; +const DEGREES_TO_RADIANS = PI / 180.0; +const RADIANS_TO_DEGREES = 180.0 / PI; + +const NO_SELECTION = "No selection"; + +const GROUPS = [ + { + id: "base", + properties: [ + { + label: NO_SELECTION, + type: "icon", + icons: ICON_FOR_TYPE, + propertyName: "type", + }, + { + label: "Name", + type: "string", + propertyName: "name", + }, + { + label: "ID", + type: "string", + propertyName: "id", + readOnly: true, + }, + { + label: "Parent", + type: "string", + propertyName: "parentID", + }, + { + label: "Locked", + glyph: "", + type: "bool", + propertyName: "locked", + }, + { + label: "Visible", + glyph: "", + type: "bool", + propertyName: "visible", + }, + ] + }, + { + id: "shape", + addToGroup: "base", + properties: [ + { + label: "Shape", + type: "dropdown", + options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, + propertyName: "shape", + }, + { + label: "Color", + type: "color", + propertyName: "color", + }, + /* + { + label: "Material", + type: "string", + propertyName: "", + }, + */ + ] + }, + { + id: "text", + addToGroup: "base", + properties: [ + { + label: "Text", + type: "string", + propertyName: "text", + }, + { + label: "Text Color", + type: "color", + propertyName: "textColor", + }, + { + label: "Background Color", + type: "color", + propertyName: "backgroundColor", + }, + /* + { + label: "Transparent Background", + type: "bool", + propertyName: "" + }, + */ + { + label: "Line Height", + type: "number", + min: 0, + step: 0.005, + fixedDecimals: 4, + unit: "m", + propertyName: "lineHeight" + }, + { + label: "Face Camera", + type: "bool", + propertyName: "faceCamera" + }, + ] + }, + { + id: "zone", + addToGroup: "base", + properties: [ + { + label: "Flying Allowed", + type: "bool", + propertyName: "flyingAllowed", + }, + { + label: "Ghosting Allowed", + type: "bool", + propertyName: "ghostingAllowed", + }, + { + label: "Filter", + type: "string", + propertyName: "filterURL", + }, + { + label: "Key Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "keyLightMode", + + }, + { + label: "Key Light Color", + type: "color", + propertyName: "keyLight.color", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Intensity", + type: "number", + min: 0, + max: 10, + step: 0.1, + fixedDecimals: 2, + propertyName: "keyLight.intensity", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Altitude", + type: "number", + fixedDecimals: 2, + unit: "deg", + propertyName: "keyLight.direction.y", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Light Azimuth", + type: "number", + fixedDecimals: 2, + unit: "deg", + propertyName: "keyLight.direction.x", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Cast Shadows", + type: "bool", + propertyName: "keyLight.castShadows", + showPropertyRule: { "keyLightMode": "enabled" }, + }, + { + label: "Skybox", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "skyboxMode", + }, + { + label: "Skybox Color", + type: "color", + propertyName: "skybox.color", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Skybox URL", + type: "string", + propertyName: "skybox.url", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + type: "buttons", + buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], + propertyName: "copyURLToAmbient", + showPropertyRule: { "skyboxMode": "enabled" }, + }, + { + label: "Ambient Light", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "ambientLightMode", + }, + { + label: "Ambient Intensity", + type: "number", + min: 0, + max: 10, + step: 0.1, + fixedDecimals: 2, + propertyName: "ambientLight.ambientIntensity", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + label: "Ambient URL", + type: "string", + propertyName: "ambientLight.ambientURL", + showPropertyRule: { "ambientLightMode": "enabled" }, + }, + { + label: "Haze", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "hazeMode", + }, + { + label: "Range", + type: "number", + min: 5, + max: 10000, + step: 5, + fixedDecimals: 0, + unit: "m", + propertyName: "haze.hazeRange", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Use Altitude", + type: "bool", + propertyName: "haze.hazeAltitudeEffect", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Base", + type: "number", + min: -1000, + max: 1000, + step: 10, + fixedDecimals: 0, + unit: "m", + propertyName: "haze.hazeBaseRef", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Ceiling", + type: "number", + min: -1000, + max: 5000, + step: 10, + fixedDecimals: 0, + unit: "m", + propertyName: "haze.hazeCeiling", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Haze Color", + type: "color", + propertyName: "haze.hazeColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Background Blend", + type: "number", + min: 0.0, + max: 1.0, + step: 0.01, + fixedDecimals: 2, + propertyName: "haze.hazeBackgroundBlend", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Enable Glare", + type: "bool", + propertyName: "haze.hazeEnableGlare", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Color", + type: "color", + propertyName: "haze.hazeGlareColor", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Glare Angle", + type: "number", + min: 0, + max: 180, + step: 1, + fixedDecimals: 0, + propertyName: "haze.hazeGlareAngle", + showPropertyRule: { "hazeMode": "enabled" }, + }, + { + label: "Bloom", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyName: "bloomMode", + }, + { + label: "Bloom Intensity", + type: "number", + min: 0, + max: 1, + step: 0.01, + fixedDecimals: 2, + propertyName: "bloom.bloomIntensity", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Threshold", + type: "number", + min: 0, + min: 1, + step: 0.01, + fixedDecimals: 2, + propertyName: "bloom.bloomThreshold", + showPropertyRule: { "bloomMode": "enabled" }, + }, + { + label: "Bloom Size", + type: "number", + min: 0, + min: 2, + step: 0.01, + fixedDecimals: 2, + propertyName: "bloom.bloomSize", + showPropertyRule: { "bloomMode": "enabled" }, + }, + ] + }, + { + id: "model", + addToGroup: "base", + properties: [ + { + label: "Model", + type: "string", + propertyName: "modelURL", + }, + { + label: "Collision Shape", + type: "dropdown", + options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, + propertyName: "shapeType", + }, + { + label: "Compound Shape", + type: "string", + propertyName: "compoundShapeURL", + }, + { + label: "Animation", + type: "string", + propertyName: "animation.url", + }, + { + label: "Play Automatically", + type: "bool", + propertyName: "animation.running", + }, + { + label: "Allow Transition", + type: "bool", + propertyName: "animation.allowTranslation", + }, + { + label: "Loop", + type: "bool", + propertyName: "animation.loop", + }, + { + label: "Hold", + type: "bool", + propertyName: "animation.hold", + }, + { + label: "Animation Frame", + type: "number", + propertyName: "animation.currentFrame", + }, + { + label: "First Frame", + type: "number", + propertyName: "animation.firstFrame", + }, + { + label: "Last Frame", + type: "number", + propertyName: "animation.lastFrame", + }, + { + label: "Animation FPS", + type: "number", + propertyName: "animation.fps", + }, + { + label: "Texture", + type: "textarea", + propertyName: "textures", + }, + { + label: "Original Texture", + type: "textarea", + propertyName: "originalTextures", + readOnly: true, + }, + ] + }, + { + id: "image", + addToGroup: "base", + properties: [ + { + label: "Image", + type: "string", + propertyName: "image", + }, + ] + }, + { + id: "web", + addToGroup: "base", + properties: [ + { + label: "Source", + type: "string", + propertyName: "sourceUrl", + }, + { + label: "Source Resolution", + type: "number", + propertyName: "dpi", + }, + ] + }, + { + id: "light", + addToGroup: "base", + properties: [ + { + label: "Light Color", + type: "color", + propertyName: "lightColor", // this actually shares "color" property with shape Color + // but separating naming here to separate property fields + }, + { + label: "Intensity", + type: "number", + min: 0, + step: 0.1, + fixedDecimals: 1, + propertyName: "intensity", + }, + { + label: "Fall-Off Radius", + type: "number", + min: 0, + step: 0.1, + fixedDecimals: 1, + unit: "m", + propertyName: "falloffRadius", + }, + { + label: "Spotlight", + type: "bool", + propertyName: "isSpotlight", + }, + { + label: "Spotlight Exponent", + type: "number", + step: 0.01, + fixedDecimals: 2, + propertyName: "exponent", + }, + { + label: "Spotlight Cut-Off", + type: "number", + step: 0.01, + fixedDecimals: 2, + propertyName: "cutoff", + }, + ] + }, + { + id: "material", + addToGroup: "base", + properties: [ + { + label: "Material URL", + type: "string", + propertyName: "materialURL", + }, + { + label: "Material Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, + { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], + propertyName: "materialData", + }, + { + label: "Submesh to Replace", + type: "number", + min: 0, + step: 1, + propertyName: "submeshToReplace", + }, + { + label: "Material Name to Replace", + type: "string", + propertyName: "materialNameToReplace", + }, + { + label: "Select Submesh", + type: "bool", + propertyName: "selectSubmesh", + }, + { + label: "Priority", + type: "number", + min: 0, + propertyName: "priority", + }, + { + label: "Material Position", + type: "vec2", + min: 0, + min: 1, + step: 0.1, + vec2Type: "xy", + subLabels: [ "x", "y" ], + propertyName: "materialMappingPos", + }, + { + label: "Material Scale", + type: "vec2", + min: 0, + step: 0.1, + vec2Type: "wh", + subLabels: [ "width", "height" ], + propertyName: "materialMappingScale", + }, + { + label: "Material Rotation", + type: "number", + step: 0.1, + fixedDecimals: 2, + unit: "deg", + propertyName: "materialMappingRot", + }, + ] + }, + { + id: "spatial", + label: "SPATIAL", + properties: [ + { + label: "Position", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyName: "position", + }, + { + label: "Rotation", + type: "vec3", + step: 0.1, + vec3Type: "pyr", + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg", + propertyName: "rotation", + }, + { + label: "Dimension", + type: "vec3", + step: 0.1, + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m", + propertyName: "dimensions", + }, + { + label: "Scale", + type: "number", + defaultValue: 100, + unit: "%", + buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, + { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], + propertyName: "scale", + }, + { + label: "Pivot", + type: "vec3", + step: 0.1, + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "(ratio of dimension)", + propertyName: "registrationPoint", + }, + { + label: "Align", + type: "buttons", + buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, + { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], + propertyName: "alignToGrid", + }, + ] + }, + { + id: "collision", + label: "COLLISION", + twoColumn: true, + properties: [ + { + label: "Collides", + type: "bool", + propertyName: "collisionless", + inverse: true, + column: -1, // before two columns div + }, + { + label: "Dynamic", + type: "bool", + propertyName: "dynamic", + column: -1, // before two columns div + }, + { + label: "Collides With", + type: "sub-header", + propertyName: "collidesWithHeader", + showPropertyRule: { "collisionless": "false" }, + column: 1, + }, + { + label: "", + type: "sub-header", + propertyName: "collidesWithHeaderHelper", + showPropertyRule: { "collisionless": "false" }, + column: 2, + }, + { + label: "Static Entities", + type: "bool", + propertyName: "static", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, + }, + { + label: "Dynamic Entities", + type: "bool", + propertyName: "dynamic", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 2, + }, + { + label: "Kinematic Entities", + type: "bool", + propertyName: "kinematic", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, + }, + { + label: "My Avatar", + type: "bool", + propertyName: "myAvatar", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 2, + }, + { + label: "Other Avatars", + type: "bool", + propertyName: "otherAvatar", + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, + }, + { + label: "Collision sound URL", + type: "string", + propertyName: "collisionSoundURL", + showPropertyRule: { "collisionless": "false" }, + }, + ] + }, + { + id: "behavior", + label: "BEHAVIOR", + twoColumn: true, + properties: [ + { + label: "Grabbable", + type: "bool", + propertyName: "grabbable", + column: 1, + }, + { + label: "Triggerable", + type: "bool", + propertyName: "triggerable", + column: 2, + }, + { + label: "Cloneable", + type: "bool", + propertyName: "cloneable", + column: 1, + }, + { + label: "Ignore inverse kinematics", + type: "bool", + propertyName: "ignoreIK", + column: 2, + }, + { + label: "Clone Lifetime", + type: "number", + unit: "s", + propertyName: "cloneLifetime", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Limit", + type: "number", + propertyName: "cloneLimit", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Dynamic", + type: "bool", + propertyName: "cloneDynamic", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Avatar Entity", + type: "bool", + propertyName: "cloneAvatarEntity", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Can cast shadow", + type: "bool", + propertyName: "castShadow", + }, + { + label: "Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], + propertyName: "script", + }, + { + label: "Server Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], + propertyName: "serverScripts", + }, + { + label: "Lifetime", + type: "number", + unit: "s", + propertyName: "lifetime", + }, + { + label: "User Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + propertyName: "userData", + }, + ] + }, + { + id: "physics", + label: "PHYSICS", + properties: [ + { + label: "Linear Velocity", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s", + propertyName: "velocity", + }, + { + label: "Linear Damping", + type: "number", + fixedDecimals: 2, + propertyName: "damping", + }, + { + label: "Angular Velocity", + type: "vec3", + multiplier: DEGREES_TO_RADIANS, + vec3Type: "pyr", + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg/s", + propertyName: "angularVelocity", + }, + { + label: "Angular Damping", + type: "number", + fixedDecimals: 4, + propertyName: "angularDamping", + }, + { + label: "Bounciness", + type: "number", + fixedDecimals: 4, + propertyName: "restitution", + }, + { + label: "Friction", + type: "number", + fixedDecimals: 4, + propertyName: "friction", + }, + { + label: "Density", + type: "number", + fixedDecimals: 4, + propertyName: "density", + }, + { + label: "Gravity", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s2", + propertyName: "gravity", + }, + { + label: "Acceleration", + type: "vec3", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s2", + propertyName: "acceleration", + }, + ] + }, +]; + +const GROUPS_PER_TYPE = { + None: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], + Shape: [ 'base', 'shape', 'spatial', 'collision', 'behavior', 'physics' ], + Text: [ 'base', 'text', 'spatial', 'collision', 'behavior', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'collision', 'behavior', 'physics' ], + Model: [ 'base', 'model', 'spatial', 'collision', 'behavior', 'physics' ], + Image: [ 'base', 'image', 'spatial', 'collision', 'behavior', 'physics' ], + Web: [ 'base', 'web', 'spatial', 'collision', 'behavior', 'physics' ], + Light: [ 'base', 'light', 'spatial', 'collision', 'behavior', 'physics' ], + Material: [ 'base', 'material', 'spatial', 'behavior' ], + ParticleEffect: [ 'base', 'spatial', 'behavior', 'physics' ], + Multiple: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], }; -var EDITOR_TIMEOUT_DURATION = 1500; -var KEY_P = 80; // Key code for letter p used for Parenting hotkey. +const EDITOR_TIMEOUT_DURATION = 1500; +const KEY_P = 80; // Key code for letter p used for Parenting hotkey. + +const MATERIAL_PREFIX_STRING = "mat::"; + +const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; + +var elGroups = {}; +var elPropertyElements = {}; +var showPropertyRules = {}; +var subProperties = []; +var icons = {}; var colorPickers = {}; var lastEntityID = null; -var MATERIAL_PREFIX_STRING = "mat::"; - -var PENDING_SCRIPT_STATUS = "[ Fetching status ]"; - function debugPrint(message) { EventBridge.emitWebEvent( JSON.stringify({ @@ -64,7 +946,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); enableChildren(document, ".colpick"); - var elLocked = document.getElementById("property-locked"); + var elLocked = elPropertyElements["locked"]; if (elLocked.checked === false) { removeStaticUserData(); @@ -72,20 +954,19 @@ function enableProperties() { } } - function disableProperties() { disableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); disableChildren(document, ".colpick"); for (var pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - var elLocked = document.getElementById("property-locked"); + var elLocked = elPropertyElements["locked"]; if (elLocked.checked === true) { - if ($('#userdata-editor').css('display') === "block") { + if ($('#property-userData-editor').css('display') === "block") { showStaticUserData(); } - if ($('#materialdata-editor').css('display') === "block") { + if ($('#property-materialData-editor').css('display') === "block") { showStaticMaterialData(); } } @@ -99,7 +980,21 @@ function showElements(els, show) { function updateProperty(propertyName, propertyValue) { var properties = {}; - properties[propertyName] = propertyValue; + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + properties[propertyGroupName] = {}; + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + properties[propertyGroupName][subPropertyName] = {}; + properties[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; + } else { + properties[propertyGroupName][subPropertyName] = propertyValue; + } + } else { + properties[propertyName] = propertyValue; + } updateProperties(properties); } @@ -111,18 +1006,9 @@ function updateProperties(properties) { })); } -function createEmitCheckedPropertyUpdateFunction(propertyName) { +function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { return function() { - updateProperty(propertyName, this.checked); - }; -} - -function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][propertyName] = this.checked; - updateProperties(properties); + updateProperty(propertyName, inverse ? !this.checked : this.checked); }; } @@ -134,15 +1020,6 @@ function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { }; } -function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][propertyName] = this.value; - updateProperties(properties); - }; -} - function createImageURLUpdateFunction(propertyName) { return function () { var newTextures = JSON.stringify({ "tex.picture": this.value }); @@ -156,33 +1033,6 @@ function createEmitTextPropertyUpdateFunction(propertyName) { }; } -function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentModeInherit, - zoneComponentModeDisabled, zoneComponentModeEnabled) { - - return function() { - var zoneComponentMode; - - if (zoneComponentModeInherit.checked) { - zoneComponentMode = 'inherit'; - } else if (zoneComponentModeDisabled.checked) { - zoneComponentMode = 'disabled'; - } else if (zoneComponentModeEnabled.checked) { - zoneComponentMode = 'enabled'; - } - - updateProperty(zoneComponent, zoneComponentMode); - }; -} - -function createEmitGroupTextPropertyUpdateFunction(group, propertyName) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][propertyName] = this.value; - updateProperties(properties); - }; -} - function createEmitVec2PropertyUpdateFunction(property, elX, elY) { return function () { var properties = {}; @@ -194,23 +1044,21 @@ function createEmitVec2PropertyUpdateFunction(property, elX, elY) { }; } -function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { - return function() { +function createEmitVec2PropertyUpdateFunctionWithMultiplier(property, elX, elY, multiplier) { + return function () { var properties = {}; properties[property] = { - x: elX.value, - y: elY.value, - z: elZ.value + x: elX.value * multiplier, + y: elY.value * multiplier }; updateProperties(properties); }; } -function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) { +function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { return function() { var properties = {}; - properties[group] = {}; - properties[group][property] = { + properties[property] = { x: elX.value, y: elY.value, z: elZ ? elZ.value : 0 @@ -256,20 +1104,6 @@ function emitColorPropertyUpdate(property, red, green, blue, group) { updateProperties(properties); } - -function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) { - return function() { - var properties = {}; - properties[group] = {}; - properties[group][property] = { - red: elRed.value, - green: elGreen.value, - blue: elBlue.value - }; - updateProperties(properties); - }; -} - function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { if (subPropertyElement.checked) { if (propertyValue.indexOf(subPropertyString)) { @@ -282,6 +1116,30 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen updateProperty(propertyName, propertyValue); } +function clearUserData() { + let elUserData = elPropertyElements["userData"]; + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + updateProperty('userData', elUserData.value); +} + +function newJSONEditor() { + deleteJSONEditor(); + createJSONEditor(); + var data = {}; + setEditorJSON(data); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + showSaveUserDataButton(); +} + +function saveUserData() { + saveJSONUserData(true); +} + function setUserDataFromEditor(noUpdate) { var json = null; try { @@ -315,7 +1173,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r var parsedData = {}; var keysToBeRemoved = removeKeys ? removeKeys : []; try { - if ($('#userdata-editor').css('height') !== "0px") { + if ($('#property-userData-editor').css('height') !== "0px") { // if there is an expanded, we want to use its json. parsedData = getEditorJSON(); } else { @@ -371,6 +1229,135 @@ function userDataChanger(groupName, keyName, values, userDataElement, defaultVal multiDataUpdater(groupName, val, userDataElement, def, removeKeys); } +var editor = null; + +function createJSONEditor() { + var container = document.getElementById("property-userData-editor"); + var options = { + search: false, + mode: 'tree', + modes: ['code', 'tree'], + name: 'userData', + onModeChange: function() { + $('.jsoneditor-poweredBy').remove(); + }, + onError: function(e) { + alert('JSON editor:' + e); + }, + onChange: function() { + var currentJSONString = editor.getText(); + + if (currentJSONString === '{"":""}') { + return; + } + $('#property-userData-button-save').attr('disabled', false); + + + } + }; + editor = new JSONEditor(container, options); +} + +function showSaveUserDataButton() { + $('#property-userData-button-save').show(); +} + +function hideSaveUserDataButton() { + $('#property-userData-button-save').hide(); +} + +function disableSaveUserDataButton() { + $('#property-userData-button-save').attr('disabled', true); +} + +function showNewJSONEditorButton() { + $('#property-userData-button-edit').show(); +} + +function hideNewJSONEditorButton() { + $('#property-userData-button-edit').hide(); +} + +function showUserDataTextArea() { + $('#property-userData').show(); +} + +function hideUserDataTextArea() { + $('#property-userData').hide(); +} + +function hideUserDataSaved() { + $('#property-userData-saved').hide(); +} + +function showStaticUserData() { + if (editor !== null) { + $('#property-userData-static').show(); + $('#property-userData-static').css('height', $('#property-userData-editor').height()); + $('#property-userData-static').text(editor.getText()); + } +} + +function removeStaticUserData() { + $('#property-userData-static').hide(); +} + +function setEditorJSON(json) { + editor.set(json); + if (editor.hasOwnProperty('expandAll')) { + editor.expandAll(); + } +} + +function getEditorJSON() { + return editor.get(); +} + +function deleteJSONEditor() { + if (editor !== null) { + editor.destroy(); + editor = null; + } +} + +var savedJSONTimer = null; + +function saveJSONUserData(noUpdate) { + setUserDataFromEditor(noUpdate); + $('#property-userData-saved').show(); + $('#property-userData-button-save').attr('disabled', true); + if (savedJSONTimer !== null) { + clearTimeout(savedJSONTimer); + } + savedJSONTimer = setTimeout(function() { + hideUserDataSaved(); + }, EDITOR_TIMEOUT_DURATION); +} + +function clearMaterialData() { + let elMaterialData = elPropertyElements["materialData"]; + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + updateProperty('materialData', elMaterialData.value); +} + +function newJSONMaterialEditor() { + deleteJSONMaterialEditor(); + createJSONMaterialEditor(); + var data = {}; + setMaterialEditorJSON(data); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + showSaveMaterialDataButton(); +} + +function saveMaterialData() { + saveJSONMaterialData(true); +} + function setMaterialDataFromEditor(noUpdate) { var json = null; try { @@ -399,16 +1386,10 @@ function setMaterialDataFromEditor(noUpdate) { } } -function setTextareaScrolling(element) { - var isScrolling = element.scrollHeight > element.offsetHeight; - element.setAttribute("scrolling", isScrolling ? "true" : "false"); -} - - var materialEditor = null; function createJSONMaterialEditor() { - var container = document.getElementById("materialdata-editor"); + var container = document.getElementById("property-materialData-editor"); var options = { search: false, mode: 'tree', @@ -426,7 +1407,7 @@ function createJSONMaterialEditor() { if (currentJSONString === '{"":""}') { return; } - $('#materialdata-save').attr('disabled', false); + $('#property-materialData-button-save').attr('disabled', false); } @@ -434,40 +1415,48 @@ function createJSONMaterialEditor() { materialEditor = new JSONEditor(container, options); } -function hideNewJSONMaterialEditorButton() { - $('#materialdata-new-editor').hide(); -} - function showSaveMaterialDataButton() { - $('#materialdata-save').show(); + $('#property-materialData-button-save').show(); } function hideSaveMaterialDataButton() { - $('#materialdata-save').hide(); + $('#property-materialData-button-save').hide(); +} + +function disableSaveMaterialDataButton() { + $('#property-materialData-button-save').attr('disabled', true); } function showNewJSONMaterialEditorButton() { - $('#materialdata-new-editor').show(); + $('#property-materialData-button-edit').show(); +} + +function hideNewJSONMaterialEditorButton() { + $('#property-materialData-button-edit').hide(); } function showMaterialDataTextArea() { - $('#property-material-data').show(); + $('#property-materialData').show(); } function hideMaterialDataTextArea() { - $('#property-material-data').hide(); + $('#property-materialData').hide(); +} + +function hideMaterialDataSaved() { + $('#property-materialData-saved').hide(); } function showStaticMaterialData() { if (materialEditor !== null) { - $('#static-materialdata').show(); - $('#static-materialdata').css('height', $('#materialdata-editor').height()); - $('#static-materialdata').text(materialEditor.getText()); + $('#property-materialData-static').show(); + $('#property-materialData-static').css('height', $('#property-materialData-editor').height()); + $('#property-materialData-static').text(materialEditor.getText()); } } function removeStaticMaterialData() { - $('#static-materialdata').hide(); + $('#property-materialData-static').hide(); } function setMaterialEditorJSON(json) { @@ -492,112 +1481,13 @@ var savedMaterialJSONTimer = null; function saveJSONMaterialData(noUpdate) { setMaterialDataFromEditor(noUpdate); - $('#materialdata-saved').show(); - $('#materialdata-save').attr('disabled', true); + $('#property-materialData-saved').show(); + $('#property-materialData-button-save').attr('disabled', true); if (savedMaterialJSONTimer !== null) { clearTimeout(savedMaterialJSONTimer); } savedMaterialJSONTimer = setTimeout(function() { - $('#materialdata-saved').hide(); - - }, EDITOR_TIMEOUT_DURATION); -} - -var editor = null; - -function createJSONEditor() { - var container = document.getElementById("userdata-editor"); - var options = { - search: false, - mode: 'tree', - modes: ['code', 'tree'], - name: 'userData', - onModeChange: function() { - $('.jsoneditor-poweredBy').remove(); - }, - onError: function(e) { - alert('JSON editor:' + e); - }, - onChange: function() { - var currentJSONString = editor.getText(); - - if (currentJSONString === '{"":""}') { - return; - } - $('#userdata-save').attr('disabled', false); - - - } - }; - editor = new JSONEditor(container, options); -} - -function hideNewJSONEditorButton() { - $('#userdata-new-editor').hide(); -} - -function showSaveUserDataButton() { - $('#userdata-save').show(); -} - -function hideSaveUserDataButton() { - $('#userdata-save').hide(); -} - -function showNewJSONEditorButton() { - $('#userdata-new-editor').show(); -} - -function showUserDataTextArea() { - $('#property-user-data').show(); -} - -function hideUserDataTextArea() { - $('#property-user-data').hide(); -} - -function showStaticUserData() { - if (editor !== null) { - $('#static-userdata').show(); - $('#static-userdata').css('height', $('#userdata-editor').height()); - $('#static-userdata').text(editor.getText()); - } -} - -function removeStaticUserData() { - $('#static-userdata').hide(); -} - -function setEditorJSON(json) { - editor.set(json); - if (editor.hasOwnProperty('expandAll')) { - editor.expandAll(); - } -} - -function getEditorJSON() { - return editor.get(); -} - -function deleteJSONEditor() { - if (editor !== null) { - editor.destroy(); - editor = null; - } -} - -var savedJSONTimer = null; - -function saveJSONUserData(noUpdate) { - setUserDataFromEditor(noUpdate); - $('#userdata-saved').show(); - $('#userdata-save').attr('disabled', true); - if (savedJSONTimer !== null) { - clearTimeout(savedJSONTimer); - } - savedJSONTimer = setTimeout(function() { - $('#userdata-saved').hide(); - + hideMaterialDataSaved(); }, EDITOR_TIMEOUT_DURATION); } @@ -610,14 +1500,14 @@ function bindAllNonJSONEditorElements() { // TODO FIXME: (JSHint) Functions declared within loops referencing // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { - if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear" || e.target.id === "materialdata-new-editor" || e.target.id === "materialdata-clear") { + if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { return; } else { - if ($('#userdata-editor').css('height') !== "0px") { - saveJSONUserData(true); + if ($('#property-userData-editor').css('height') !== "0px") { + saveUserData(); } - if ($('#materialdata-editor').css('height') !== "0px") { - saveJSONMaterialData(true); + if ($('#property-materialData-editor').css('height') !== "0px") { + saveMaterialData(); } } }); @@ -634,264 +1524,575 @@ function unbindAllInputs() { } } +function setTextareaScrolling(element) { + var isScrolling = element.scrollHeight > element.offsetHeight; + element.setAttribute("scrolling", isScrolling ? "true" : "false"); +} + function showParentMaterialNameBox(number, elNumber, elString) { if (number) { - $('#property-parent-material-id-number-container').show(); - $('#property-parent-material-id-string-container').hide(); + $('#property-submeshToReplace').parent().show(); + $('#property-materialNameToReplace').parent().hide(); elString.value = ""; } else { - $('#property-parent-material-id-string-container').show(); - $('#property-parent-material-id-number-container').hide(); + $('#property-materialNameToReplace').parent().show(); + $('#property-submeshToReplace').parent().hide(); elNumber.value = 0; } } +function rescaleDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseFloat(document.getElementById("property-scale").value) + })); +} + +function moveSelectionToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveSelectionToGrid" + })); +} + +function moveAllToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveAllToGrid" + })); +} + +function resetToNaturalDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "resetToNaturalDimensions" + })); +} + +function reloadScripts() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadClientScripts" + })); +} + +function reloadServerScripts() { + // invalidate the current status (so that same-same updates can still be observed visually) + document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadServerScripts" + })); +} + +function copySkyboxURLToAmbientURL() { + let skyboxURL = elPropertyElements["skybox.url"].value; + elPropertyElements["ambientLight.ambientURL"].value = skyboxURL; + updateProperty("ambientLight.ambientURL", skyboxURL); +} + +function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { + let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; + elLabel.setAttribute("for", elementPropertyID); + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementPropertyID); + elInput.setAttribute("type", "number"); + elInput.setAttribute("class", subLabel); + if (min !== undefined) { + elInput.setAttribute("min", min); + } + if (max !== undefined) { + elInput.setAttribute("max", max); + } + if (step !== undefined) { + elInput.setAttribute("step", step); + } + elDiv.appendChild(elInput); + elDiv.appendChild(elLabel); + elTuple.appendChild(elDiv); + return elInput; +} + +function addUnit(unit, elLabel) { + if (unit !== undefined) { + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "unit"); + elSpan.innerHTML = unit; + elLabel.appendChild(elSpan); + } +} + +function addButtons(elProperty, propertyID, buttons, newRow) { + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "row"); + + buttons.forEach(function(button) { + let elButton = document.createElement('input'); + elButton.setAttribute("type", "button"); + elButton.setAttribute("class", button.className); + elButton.setAttribute("id", propertyID + "-button-" + button.id); + elButton.setAttribute("value", button.label); + elButton.addEventListener("click", button.onClick); + if (newRow) { + elDiv.appendChild(elButton); + } else { + elProperty.appendChild(elButton); + } + }); + + if (newRow) { + elProperty.appendChild(document.createElement('br')); + elProperty.appendChild(elDiv); + } +} + +function showGroupsForType(type) { + if (type === "Box" || type === "Sphere") { + type = "Shape"; + } + + let typeGroups = GROUPS_PER_TYPE[type]; + + for (let groupKey in elGroups) { + let elGroup = elGroups[groupKey]; + if (typeGroups && typeGroups.indexOf(groupKey) > -1) { + elGroup.style.display = "block"; + } else { + elGroup.style.display = "none"; + } + } +} + +function resetProperties() { + for (let propertyName in elPropertyElements) { + let elProperty = elPropertyElements[propertyName]; + if (elProperty instanceof Array) { + if (elProperty.length === 2 || elProperty.length === 3) { + // vec2/vec3 are array of 2/3 input numbers + elProperty[0].value = ""; + elProperty[1].value = ""; + if (elProperty[2] !== undefined) { + elProperty[2].value = ""; + } + } else if (elProperty.length === 4) { + // color is array of color picker and 3 input numbers + elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elProperty[1].value = ""; + elProperty[2].value = ""; + elProperty[3].value = ""; + } + } else if (elProperty.getAttribute("type") === "number") { + if (elProperty.getAttribute("defaultValue")) { + elProperty.value = elProperty.getAttribute("defaultValue"); + } else { + elProperty.value = ""; + } + } else if (elProperty.getAttribute("type") === "checkbox") { + elProperty.checked = false; + } else { + elProperty.value = ""; + } + } + + for (let showPropertyRule in showPropertyRules) { + let propertyShowRules = showPropertyRules[showPropertyRule]; + for (let propertyToHide in propertyShowRules) { + let elPropertyToHide = elPropertyElements[propertyToHide]; + if (elPropertyToHide) { + let parentNode = elPropertyToHide.parentNode; + if (parentNode === undefined && elPropertyToHide instanceof Array) { + parentNode = elPropertyToHide[0].parentNode; + } + parentNode.style.display = "none"; + } + } + } +} + function loaded() { - openEventBridge(function() { + openEventBridge(function() { + let elPropertiesList = document.getElementById("properties-list"); + + GROUPS.forEach(function(group) { + let elGroup; + if (group.addToGroup !== undefined) { + let fieldset = document.getElementById(group.addToGroup); + elGroup = document.createElement('div'); + fieldset.appendChild(elGroup); + } else { + elGroup = document.createElement('fieldset'); + elGroup.setAttribute("class", "major"); + elGroup.setAttribute("id", group.id); + elPropertiesList.appendChild(elGroup); + } - var elPropertiesList = document.getElementById("properties-list"); - var elID = document.getElementById("property-id"); - var elType = document.getElementById("property-type"); - var elTypeIcon = document.getElementById("type-icon"); - var elName = document.getElementById("property-name"); - var elLocked = document.getElementById("property-locked"); - var elVisible = document.getElementById("property-visible"); - var elPositionX = document.getElementById("property-pos-x"); - var elPositionY = document.getElementById("property-pos-y"); - var elPositionZ = document.getElementById("property-pos-z"); - var elMoveSelectionToGrid = document.getElementById("move-selection-to-grid"); - var elMoveAllToGrid = document.getElementById("move-all-to-grid"); + if (group.label !== undefined) { + let elLegend = document.createElement('legend'); + elLegend.innerText = group.label; + elLegend.setAttribute("class", "section-header"); + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", ".collapse-icon"); + elSpan.innerText = "M"; + elLegend.appendChild(elSpan); + elGroup.appendChild(elLegend); + } + + group.properties.forEach(function(property) { + let propertyType = property.type; + let propertyName = property.propertyName; + let propertyID = "property-" + propertyName; + propertyID = propertyID.replace(".", "-"); + + let elProperty; + if (propertyType === "sub-header") { + elProperty = document.createElement('legend'); + elProperty.innerText = property.label; + elProperty.setAttribute("class", "sub-section-header"); + } else { + elProperty = document.createElement('div'); + elProperty.setAttribute("id", "div-" + propertyName); + } + + if (group.twoColumn && property.column !== undefined && property.column !== -1) { + let columnName = group.id + "column" + property.column; + let elColumn = document.getElementById(columnName); + if (!elColumn) { + let columnDivName = group.id + "columnDiv"; + let elColumnDiv = document.getElementById(columnDivName); + if (!elColumnDiv) { + elColumnDiv = document.createElement('div'); + elColumnDiv.setAttribute("class", "two-column"); + elColumnDiv.setAttribute("id", group.id + "columnDiv"); + elGroup.appendChild(elColumnDiv); + } + elColumn = document.createElement('fieldset'); + elColumn.setAttribute("class", "column"); + elColumn.setAttribute("id", columnName); + elColumnDiv.appendChild(elColumn); + } + elColumn.appendChild(elProperty); + } else { + elGroup.appendChild(elProperty); + } + + let elLabel = document.createElement('label'); + elLabel.innerText = property.label; + elLabel.setAttribute("for", propertyID); + + switch (propertyType) { + case 'vec3': { + elProperty.setAttribute("class", "property " + property.vec3Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(property.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); + let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); + let elInputZ = createTupleNumberInput(elTuple, propertyID, property.subLabels[2], property.min, property.max, property.step); + + let inputChangeFunction; + let multiplier = 1; + if (property.multiplier !== undefined) { + multiplier = property.multiplier; + inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); + } else { + inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, elInputZ, property.multiplier); + } + elInputX.setAttribute("multiplier", multiplier); + elInputY.setAttribute("multiplier", multiplier); + elInputZ.setAttribute("multiplier", multiplier); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + elInputZ.addEventListener('change', inputChangeFunction); + + elPropertyElements[propertyName] = [elInputX, elInputY, elInputZ]; + break; + } + case 'vec2': { + elProperty.setAttribute("class", "property " + property.vec2Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(property.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); + let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); + + let inputChangeFunction; + let multiplier = 1; + if (property.multiplier !== undefined) { + multiplier = property.multiplier; + inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY); + } else { + inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, property.multiplier); + } + elInputX.setAttribute("multiplier", multiplier); + elInputY.setAttribute("multiplier", multiplier); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + + elPropertyElements[propertyName] = [elInputX, elInputY]; + break; + } + case 'color': { + elProperty.setAttribute("class", "property rgb fstuple"); + + let elColorPicker = document.createElement('div'); + elColorPicker.setAttribute("class", "color-picker"); + elColorPicker.setAttribute("id", propertyID); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + elProperty.appendChild(elColorPicker); + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputR = createTupleNumberInput(elTuple, propertyID, "red", 0, 255, 1); + let elInputG = createTupleNumberInput(elTuple, propertyID, "green", 0, 255, 1); + let elInputB = createTupleNumberInput(elTuple, propertyID, "blue", 0, 255, 1); + + let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); + elInputR.addEventListener('change', inputChangeFunction); + elInputG.addEventListener('change', inputChangeFunction); + elInputB.addEventListener('change', inputChangeFunction); + + let colorPickerID = "#" + propertyID; + colorPickers[colorPickerID] = $(colorPickerID).colpick({ + colorScheme: 'dark', + layout: 'hex', + color: '000000', + submit: false, // We don't want to have a submission button + onShow: function(colpick) { + $(colorPickerID).attr('active', 'true'); + // The original color preview within the picker needs to be updated on show because + // prior to the picker being shown we don't have access to the selections' starting color. + colorPickers[colorPickerID].colpickSetColor({ + "r": elInputR.value, + "g": elInputG.value, + "b": elInputB.value + }); + }, + onHide: function(colpick) { + $(colorPickerID).attr('active', 'false'); + }, + onChange: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + } + }); + + elPropertyElements[propertyName] = [elColorPicker, elInputR, elInputG, elInputB]; + break; + } + case 'string': { + elProperty.setAttribute("class", "property text"); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "text"); + if (property.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, false); + } + + elPropertyElements[propertyName] = elInput; + break; + } + case 'bool': { + elProperty.setAttribute("class", "property checkbox"); + + if (property.glyph !== undefined) { + elLabel.innerText = " " + property.label; + let elSpan = document.createElement('span'); + elSpan.innerHTML = property.glyph; + elLabel.insertBefore(elSpan, elLabel.firstChild); + } + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "checkbox"); + + let inverse = property.inverse; + elInput.setAttribute("inverse", inverse ? "true" : "false"); + + elProperty.appendChild(elInput); + elProperty.appendChild(elLabel); + + let subPropertyOf = property.subPropertyOf; + if (subPropertyOf !== undefined) { + elInput.setAttribute("subPropertyOf", subPropertyOf); + elPropertyElements[propertyName] = elInput; + subProperties.push(propertyName); + elInput.addEventListener('change', function() { + updateCheckedSubProperty(subPropertyOf, properties[subPropertyOf], elInput, propertyName); + }); + } else { + elPropertyElements[propertyName] = elInput; + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, inverse)); + } + break; + } + case 'dropdown': { + elProperty.setAttribute("class", "property dropdown"); + + let elInput = document.createElement('select'); + elInput.setAttribute("id", propertyID); + + for (let optionKey in property.options) { + let option = document.createElement('option'); + option.value = optionKey; + option.text = property.options[optionKey]; + elInput.add(option); + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + elPropertyElements[propertyName] = elInput; + break; + } + case 'number': { + elProperty.setAttribute("class", "property number"); + + addUnit(property.unit, elLabel); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "number"); + if (property.min !== undefined) { + elInput.setAttribute("min", property.min); + } + if (property.max !== undefined) { + elInput.setAttribute("max", property.max); + } + if (property.step !== undefined) { + elInput.setAttribute("step", property.step); + } + + let fixedDecimals = property.fixedDecimals !== undefined ? property.fixedDecimals : 0; + elInput.setAttribute("fixedDecimals", fixedDecimals); + + let defaultValue = property.defaultValue; + if (defaultValue !== undefined) { + elInput.setAttribute("defaultValue", defaultValue); + elInput.value = defaultValue; + } + + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, true); + } + + elPropertyElements[propertyName] = elInput; + break; + } + case 'textarea': { + elProperty.setAttribute("class", "property textarea"); + + elProperty.appendChild(elLabel); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, true); + } + + let elInput = document.createElement('textarea'); + elInput.setAttribute("id", propertyID); + if (property.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elInput); + elPropertyElements[propertyName] = elInput; + break; + } + case 'icon': { + elProperty.setAttribute("class", "property value"); + + elLabel.setAttribute("id", propertyID); + elLabel.innerHTML = " " + property.label; + + let elSpan = document.createElement('span'); + elSpan.setAttribute("id", propertyID + "-icon"); + icons[propertyName] = property.icons; - var elDimensionsX = document.getElementById("property-dim-x"); - var elDimensionsY = document.getElementById("property-dim-y"); - var elDimensionsZ = document.getElementById("property-dim-z"); - var elResetToNaturalDimensions = document.getElementById("reset-to-natural-dimensions"); - var elRescaleDimensionsPct = document.getElementById("dimension-rescale-pct"); - var elRescaleDimensionsButton = document.getElementById("dimension-rescale-button"); - - var elParentID = document.getElementById("property-parent-id"); - var elParentJointIndex = document.getElementById("property-parent-joint-index"); - - var elRegistrationX = document.getElementById("property-reg-x"); - var elRegistrationY = document.getElementById("property-reg-y"); - var elRegistrationZ = document.getElementById("property-reg-z"); - - var elRotationX = document.getElementById("property-rot-x"); - var elRotationY = document.getElementById("property-rot-y"); - var elRotationZ = document.getElementById("property-rot-z"); - - var elLinearVelocityX = document.getElementById("property-lvel-x"); - var elLinearVelocityY = document.getElementById("property-lvel-y"); - var elLinearVelocityZ = document.getElementById("property-lvel-z"); - var elLinearDamping = document.getElementById("property-ldamping"); - - var elAngularVelocityX = document.getElementById("property-avel-x"); - var elAngularVelocityY = document.getElementById("property-avel-y"); - var elAngularVelocityZ = document.getElementById("property-avel-z"); - var elAngularDamping = document.getElementById("property-adamping"); - - var elRestitution = document.getElementById("property-restitution"); - var elFriction = document.getElementById("property-friction"); - - var elGravityX = document.getElementById("property-grav-x"); - var elGravityY = document.getElementById("property-grav-y"); - var elGravityZ = document.getElementById("property-grav-z"); - - var elAccelerationX = document.getElementById("property-lacc-x"); - var elAccelerationY = document.getElementById("property-lacc-y"); - var elAccelerationZ = document.getElementById("property-lacc-z"); - - var elDensity = document.getElementById("property-density"); - var elCollisionless = document.getElementById("property-collisionless"); - var elDynamic = document.getElementById("property-dynamic"); - var elCollideStatic = document.getElementById("property-collide-static"); - var elCollideDynamic = document.getElementById("property-collide-dynamic"); - var elCollideKinematic = document.getElementById("property-collide-kinematic"); - var elCollideMyAvatar = document.getElementById("property-collide-myAvatar"); - var elCollideOtherAvatar = document.getElementById("property-collide-otherAvatar"); - var elCollisionSoundURL = document.getElementById("property-collision-sound-url"); - - var elGrabbable = document.getElementById("property-grabbable"); - - var elCloneable = document.getElementById("property-cloneable"); - var elCloneableDynamic = document.getElementById("property-cloneable-dynamic"); - var elCloneableAvatarEntity = document.getElementById("property-cloneable-avatarEntity"); - var elCloneableGroup = document.getElementById("group-cloneable-group"); - var elCloneableLifetime = document.getElementById("property-cloneable-lifetime"); - var elCloneableLimit = document.getElementById("property-cloneable-limit"); - - var elTriggerable = document.getElementById("property-triggerable"); - var elIgnoreIK = document.getElementById("property-ignore-ik"); - - var elLifetime = document.getElementById("property-lifetime"); - var elScriptURL = document.getElementById("property-script-url"); - var elScriptTimestamp = document.getElementById("property-script-timestamp"); - var elReloadScriptsButton = document.getElementById("reload-script-button"); - var elServerScripts = document.getElementById("property-server-scripts"); - var elReloadServerScriptsButton = document.getElementById("reload-server-scripts-button"); - var elServerScriptStatus = document.getElementById("server-script-status"); - var elServerScriptError = document.getElementById("server-script-error"); - var elUserData = document.getElementById("property-user-data"); - var elClearUserData = document.getElementById("userdata-clear"); - var elSaveUserData = document.getElementById("userdata-save"); - var elNewJSONEditor = document.getElementById('userdata-new-editor'); - var elColorControlVariant2 = document.getElementById("property-color-control2"); - var elColorRed = document.getElementById("property-color-red"); - var elColorGreen = document.getElementById("property-color-green"); - var elColorBlue = document.getElementById("property-color-blue"); - - var elShape = document.getElementById("property-shape"); - - var elCanCastShadow = document.getElementById("property-can-cast-shadow"); - - var elLightSpotLight = document.getElementById("property-light-spot-light"); - var elLightColor = document.getElementById("property-light-color"); - var elLightColorRed = document.getElementById("property-light-color-red"); - var elLightColorGreen = document.getElementById("property-light-color-green"); - var elLightColorBlue = document.getElementById("property-light-color-blue"); - - var elLightIntensity = document.getElementById("property-light-intensity"); - var elLightFalloffRadius = document.getElementById("property-light-falloff-radius"); - var elLightExponent = document.getElementById("property-light-exponent"); - var elLightCutoff = document.getElementById("property-light-cutoff"); - - var elModelURL = document.getElementById("property-model-url"); - var elShapeType = document.getElementById("property-shape-type"); - var elCompoundShapeURL = document.getElementById("property-compound-shape-url"); - var elModelAnimationURL = document.getElementById("property-model-animation-url"); - var elModelAnimationPlaying = document.getElementById("property-model-animation-playing"); - var elModelAnimationFPS = document.getElementById("property-model-animation-fps"); - var elModelAnimationFrame = document.getElementById("property-model-animation-frame"); - var elModelAnimationFirstFrame = document.getElementById("property-model-animation-first-frame"); - var elModelAnimationLastFrame = document.getElementById("property-model-animation-last-frame"); - var elModelAnimationLoop = document.getElementById("property-model-animation-loop"); - var elModelAnimationHold = document.getElementById("property-model-animation-hold"); - var elModelAnimationAllowTranslation = document.getElementById("property-model-animation-allow-translation"); - var elModelTextures = document.getElementById("property-model-textures"); - var elModelOriginalTextures = document.getElementById("property-model-original-textures"); - - var elMaterialURL = document.getElementById("property-material-url"); - //var elMaterialMappingMode = document.getElementById("property-material-mapping-mode"); - var elPriority = document.getElementById("property-priority"); - var elParentMaterialNameString = document.getElementById("property-parent-material-id-string"); - var elParentMaterialNameNumber = document.getElementById("property-parent-material-id-number"); - var elParentMaterialNameCheckbox = document.getElementById("property-parent-material-id-checkbox"); - var elMaterialMappingPosX = document.getElementById("property-material-mapping-pos-x"); - var elMaterialMappingPosY = document.getElementById("property-material-mapping-pos-y"); - var elMaterialMappingScaleX = document.getElementById("property-material-mapping-scale-x"); - var elMaterialMappingScaleY = document.getElementById("property-material-mapping-scale-y"); - var elMaterialMappingRot = document.getElementById("property-material-mapping-rot"); - var elMaterialData = document.getElementById("property-material-data"); - var elClearMaterialData = document.getElementById("materialdata-clear"); - var elSaveMaterialData = document.getElementById("materialdata-save"); - var elNewJSONMaterialEditor = document.getElementById('materialdata-new-editor'); - - var elImageURL = document.getElementById("property-image-url"); - - var elWebSourceURL = document.getElementById("property-web-source-url"); - var elWebDPI = document.getElementById("property-web-dpi"); - - var elDescription = document.getElementById("property-description"); - - var elHyperlinkHref = document.getElementById("property-hyperlink-href"); - - var elTextText = document.getElementById("property-text-text"); - var elTextLineHeight = document.getElementById("property-text-line-height"); - var elTextTextColor = document.getElementById("property-text-text-color"); - var elTextFaceCamera = document.getElementById("property-text-face-camera"); - var elTextTextColorRed = document.getElementById("property-text-text-color-red"); - var elTextTextColorGreen = document.getElementById("property-text-text-color-green"); - var elTextTextColorBlue = document.getElementById("property-text-text-color-blue"); - var elTextBackgroundColor = document.getElementById("property-text-background-color"); - var elTextBackgroundColorRed = document.getElementById("property-text-background-color-red"); - var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green"); - var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue"); - - // Key light - var elZoneKeyLightModeInherit = document.getElementById("property-zone-key-light-mode-inherit"); - var elZoneKeyLightModeDisabled = document.getElementById("property-zone-key-light-mode-disabled"); - var elZoneKeyLightModeEnabled = document.getElementById("property-zone-key-light-mode-enabled"); - - var elZoneKeyLightColor = document.getElementById("property-zone-key-light-color"); - var elZoneKeyLightColorRed = document.getElementById("property-zone-key-light-color-red"); - var elZoneKeyLightColorGreen = document.getElementById("property-zone-key-light-color-green"); - var elZoneKeyLightColorBlue = document.getElementById("property-zone-key-light-color-blue"); - var elZoneKeyLightIntensity = document.getElementById("property-zone-key-intensity"); - var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); - var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); - - var elZoneKeyLightCastShadows = document.getElementById("property-zone-key-light-cast-shadows"); - - // Skybox - var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); - var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); - var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); - - // Ambient light - var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); - - var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); - var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); - var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); - - var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); - var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); - - // Haze - var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit"); - var elZoneHazeModeDisabled = document.getElementById("property-zone-haze-mode-disabled"); - var elZoneHazeModeEnabled = document.getElementById("property-zone-haze-mode-enabled"); - - var elZoneHazeRange = document.getElementById("property-zone-haze-range"); - var elZoneHazeColor = document.getElementById("property-zone-haze-color"); - var elZoneHazeColorRed = document.getElementById("property-zone-haze-color-red"); - var elZoneHazeColorGreen = document.getElementById("property-zone-haze-color-green"); - var elZoneHazeColorBlue = document.getElementById("property-zone-haze-color-blue"); - var elZoneHazeGlareColor = document.getElementById("property-zone-haze-glare-color"); - var elZoneHazeGlareColorRed = document.getElementById("property-zone-haze-glare-color-red"); - var elZoneHazeGlareColorGreen = document.getElementById("property-zone-haze-glare-color-green"); - var elZoneHazeGlareColorBlue = document.getElementById("property-zone-haze-glare-color-blue"); - var elZoneHazeEnableGlare = document.getElementById("property-zone-haze-enable-light-blend"); - var elZoneHazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); - - var elZoneHazeAltitudeEffect = document.getElementById("property-zone-haze-altitude-effect"); - var elZoneHazeBaseRef = document.getElementById("property-zone-haze-base"); - var elZoneHazeCeiling = document.getElementById("property-zone-haze-ceiling"); - - var elZoneHazeBackgroundBlend = document.getElementById("property-zone-haze-background-blend"); - - // Bloom - var elZoneBloomModeInherit = document.getElementById("property-zone-bloom-mode-inherit"); - var elZoneBloomModeDisabled = document.getElementById("property-zone-bloom-mode-disabled"); - var elZoneBloomModeEnabled = document.getElementById("property-zone-bloom-mode-enabled"); - - var elZoneBloomIntensity = document.getElementById("property-zone-bloom-intensity"); - var elZoneBloomThreshold = document.getElementById("property-zone-bloom-threshold"); - var elZoneBloomSize = document.getElementById("property-zone-bloom-size"); - - var elZoneSkyboxColor = document.getElementById("property-zone-skybox-color"); - var elZoneSkyboxColorRed = document.getElementById("property-zone-skybox-color-red"); - var elZoneSkyboxColorGreen = document.getElementById("property-zone-skybox-color-green"); - var elZoneSkyboxColorBlue = document.getElementById("property-zone-skybox-color-blue"); - var elZoneSkyboxURL = document.getElementById("property-zone-skybox-url"); - - var elZoneFlyingAllowed = document.getElementById("property-zone-flying-allowed"); - var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed"); - var elZoneFilterURL = document.getElementById("property-zone-filter-url"); - - var elVoxelVolumeSizeX = document.getElementById("property-voxel-volume-size-x"); - var elVoxelVolumeSizeY = document.getElementById("property-voxel-volume-size-y"); - var elVoxelVolumeSizeZ = document.getElementById("property-voxel-volume-size-z"); - var elVoxelSurfaceStyle = document.getElementById("property-voxel-surface-style"); - var elXTextureURL = document.getElementById("property-x-texture-url"); - var elYTextureURL = document.getElementById("property-y-texture-url"); - var elZTextureURL = document.getElementById("property-z-texture-url"); - - if (window.EventBridge !== undefined) { + elProperty.appendChild(elSpan); + elProperty.appendChild(elLabel); + elPropertyElements[propertyName] = [ elSpan, elLabel ]; + break; + } + case 'buttons': { + elProperty.setAttribute("class", "property Text"); + + let hasLabel = property.label !== undefined; + if (hasLabel) { + elProperty.appendChild(elLabel); + } + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, hasLabel); + } + + elPropertyElements[propertyName] = elProperty; + break; + } + case 'sub-header': { + if (propertyName !== undefined) { + elPropertyElements[propertyName] = elProperty; + } + break; + } + } + + let showPropertyRule = property.showPropertyRule; + if (showPropertyRule !== undefined) { + let dependentProperty = Object.keys(showPropertyRule)[0]; + let dependentPropertyValue = showPropertyRule[dependentProperty]; + if (!showPropertyRules[dependentProperty]) { + showPropertyRules[dependentProperty] = []; + } + showPropertyRules[dependentProperty][propertyName] = dependentPropertyValue; + } + }); + + elGroups[group.id] = elGroup; + }); + + if (window.EventBridge !== undefined) { var properties; EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type === "server_script_status") { + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); elServerScriptError.value = data.errorInfo; // If we just set elServerScriptError's diplay to block or none, we still end up with // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. @@ -913,218 +2114,33 @@ function loaded() { elServerScriptStatus.innerText = "Not running"; } } else if (data.type === "update" && data.selections) { - if (data.selections.length === 0) { if (lastEntityID !== null) { if (editor !== null) { - saveJSONUserData(true); + saveUserData(); deleteJSONEditor(); } if (materialEditor !== null) { - saveJSONMaterialData(true); + saveMaterialData(); deleteJSONMaterialEditor(); } } - - elTypeIcon.style.display = "none"; - elType.innerHTML = "No selection"; - elPropertiesList.className = ''; - - elID.value = ""; - elName.value = ""; - elLocked.checked = false; - elVisible.checked = false; - - elParentID.value = ""; - elParentJointIndex.value = ""; - - elColorRed.value = ""; - elColorGreen.value = ""; - elColorBlue.value = ""; - elColorControlVariant2.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - - elPositionX.value = ""; - elPositionY.value = ""; - elPositionZ.value = ""; - - elRotationX.value = ""; - elRotationY.value = ""; - elRotationZ.value = ""; - - elDimensionsX.value = ""; - elDimensionsY.value = ""; - elDimensionsZ.value = ""; - - elRegistrationX.value = ""; - elRegistrationY.value = ""; - elRegistrationZ.value = ""; - - elLinearVelocityX.value = ""; - elLinearVelocityY.value = ""; - elLinearVelocityZ.value = ""; - elLinearDamping.value = ""; - - elAngularVelocityX.value = ""; - elAngularVelocityY.value = ""; - elAngularVelocityZ.value = ""; - elAngularDamping.value = ""; - - elGravityX.value = ""; - elGravityY.value = ""; - elGravityZ.value = ""; - - elAccelerationX.value = ""; - elAccelerationY.value = ""; - elAccelerationZ.value = ""; - - elRestitution.value = ""; - elFriction.value = ""; - elDensity.value = ""; - - elCollisionless.checked = false; - elDynamic.checked = false; - - elCollideStatic.checked = false; - elCollideKinematic.checked = false; - elCollideDynamic.checked = false; - elCollideMyAvatar.checked = false; - elCollideOtherAvatar.checked = false; - - elGrabbable.checked = false; - elTriggerable.checked = false; - elIgnoreIK.checked = false; - - elCloneable.checked = false; - elCloneableDynamic.checked = false; - elCloneableAvatarEntity.checked = false; - elCloneableGroup.style.display = "none"; - elCloneableLimit.value = ""; - elCloneableLifetime.value = ""; - - showElements(document.getElementsByClassName('can-cast-shadow-section'), true); - elCanCastShadow.checked = false; - - elCollisionSoundURL.value = ""; - elLifetime.value = ""; - elScriptURL.value = ""; - elServerScripts.value = ""; - elHyperlinkHref.value = ""; - elDescription.value = ""; - - deleteJSONEditor(); - elUserData.value = ""; + + elPropertyElements["type"][0].style.display = "none"; + elPropertyElements["type"][1].innerHTML = NO_SELECTION; + elPropertiesList.className = ''; + showGroupsForType("None"); + + resetProperties(); + + deleteJSONEditor(); + elPropertyElements["userData"].value = ""; showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); - - // Shape Properties - elShape.value = "Cube"; - setDropdownText(elShape); - - // Light Properties - elLightSpotLight.checked = false; - elLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elLightColorRed.value = ""; - elLightColorGreen.value = ""; - elLightColorBlue.value = ""; - elLightIntensity.value = ""; - elLightFalloffRadius.value = ""; - elLightExponent.value = ""; - elLightCutoff.value = ""; - - // Model Properties - elModelURL.value = ""; - elCompoundShapeURL.value = ""; - elShapeType.value = "none"; - setDropdownText(elShapeType); - elModelAnimationURL.value = "" - elModelAnimationPlaying.checked = false; - elModelAnimationFPS.value = ""; - elModelAnimationFrame.value = ""; - elModelAnimationFirstFrame.value = ""; - elModelAnimationLastFrame.value = ""; - elModelAnimationLoop.checked = false; - elModelAnimationHold.checked = false; - elModelAnimationAllowTranslation.checked = false; - elModelTextures.value = ""; - elModelOriginalTextures.value = ""; - - // Zone Properties - elZoneFlyingAllowed.checked = false; - elZoneGhostingAllowed.checked = false; - elZoneFilterURL.value = ""; - elZoneKeyLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneKeyLightColorRed.value = ""; - elZoneKeyLightColorGreen.value = ""; - elZoneKeyLightColorBlue.value = ""; - elZoneKeyLightIntensity.value = ""; - elZoneKeyLightDirectionX.value = ""; - elZoneKeyLightDirectionY.value = ""; - elZoneKeyLightCastShadows.checked = false; - elZoneAmbientLightIntensity.value = ""; - elZoneAmbientLightURL.value = ""; - elZoneHazeRange.value = ""; - elZoneHazeColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneHazeColorRed.value = ""; - elZoneHazeColorGreen.value = ""; - elZoneHazeColorBlue.value = ""; - elZoneHazeBackgroundBlend.value = 0; - elZoneHazeGlareColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneHazeGlareColorRed.value = ""; - elZoneHazeGlareColorGreen.value = ""; - elZoneHazeGlareColorBlue.value = ""; - elZoneHazeEnableGlare.checked = false; - elZoneHazeGlareAngle.value = ""; - elZoneHazeAltitudeEffect.checked = false; - elZoneHazeBaseRef.value = ""; - elZoneHazeCeiling.value = ""; - elZoneBloomIntensity.value = ""; - elZoneBloomThreshold.value = ""; - elZoneBloomSize.value = ""; - elZoneSkyboxColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elZoneSkyboxColorRed.value = ""; - elZoneSkyboxColorGreen.value = ""; - elZoneSkyboxColorBlue.value = ""; - elZoneSkyboxURL.value = ""; - showElements(document.getElementsByClassName('keylight-section'), true); - showElements(document.getElementsByClassName('skybox-section'), true); - showElements(document.getElementsByClassName('ambient-section'), true); - showElements(document.getElementsByClassName('haze-section'), true); - showElements(document.getElementsByClassName('bloom-section'), true); - - // Text Properties - elTextText.value = ""; - elTextLineHeight.value = ""; - elTextFaceCamera.checked = false; - elTextTextColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elTextTextColorRed.value = ""; - elTextTextColorGreen.value = ""; - elTextTextColorBlue.value = ""; - elTextBackgroundColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elTextBackgroundColorRed.value = ""; - elTextBackgroundColorGreen.value = ""; - elTextBackgroundColorBlue.value = ""; - - // Image Properties - elImageURL.value = ""; - - // Web Properties - elWebSourceURL.value = ""; - elWebDPI.value = ""; - - // Material Properties - elMaterialURL.value = ""; - elParentMaterialNameNumber.value = ""; - elParentMaterialNameCheckbox.checked = false; - elPriority.value = ""; - elMaterialMappingPosX.value = ""; - elMaterialMappingPosY.value = ""; - elMaterialMappingScaleX.value = ""; - elMaterialMappingScaleY.value = ""; - elMaterialMappingRot.value = ""; - - deleteJSONMaterialEditor(); - elMaterialData.value = ""; + + deleteJSONMaterialEditor(); + elPropertyElements["materialData"].value = ""; showMaterialDataTextArea(); showSaveMaterialDataButton(); showNewJSONMaterialEditorButton(); @@ -1133,15 +2149,16 @@ function loaded() { } else if (data.selections.length > 1) { deleteJSONEditor(); deleteJSONMaterialEditor(); - var selections = data.selections; + + let selections = data.selections; - var ids = []; - var types = {}; - var numTypes = 0; + let ids = []; + let types = {}; + let numTypes = 0; - for (var i = 0; i < selections.length; i++) { + for (let i = 0; i < selections.length; i++) { ids.push(selections[i].id); - var currentSelectedType = selections[i].properties.type; + let currentSelectedType = selections[i].properties.type; if (types[currentSelectedType] === undefined) { types[currentSelectedType] = 0; numTypes += 1; @@ -1149,400 +2166,188 @@ function loaded() { types[currentSelectedType]++; } - var type = "Multiple"; + let type = "Multiple"; if (numTypes === 1) { type = selections[0].properties.type; } - - elType.innerHTML = type + " (" + data.selections.length + ")"; - elTypeIcon.innerHTML = ICON_FOR_TYPE[type]; - elTypeIcon.style.display = "inline-block"; - elPropertiesList.className = ''; - - elID.value = ""; - - disableProperties(); + + elPropertyElements["type"][0].innerHTML = ICON_FOR_TYPE[type]; + elPropertyElements["type"][0].style.display = "inline-block"; + elPropertyElements["type"][1].innerHTML = type + " (" + data.selections.length + ")"; + elPropertiesList.className = ''; + showGroupsForType(type); + + resetProperties(); + disableProperties(); } else { - properties = data.selections[0].properties; - if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { + + if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { if (editor !== null) { - saveJSONUserData(true); + saveUserData(); } if (materialEditor !== null) { - saveJSONMaterialData(true); + saveMaterialData(); } } - var doSelectElement = lastEntityID === '"' + properties.id + '"'; + let doSelectElement = lastEntityID === '"' + properties.id + '"'; // the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + properties.id + '"'; - elID.value = properties.id; // HTML workaround since image is not yet a separate entity type - var IMAGE_MODEL_NAME = 'default-image-model.fbx'; + let IMAGE_MODEL_NAME = 'default-image-model.fbx'; if (properties.type === "Model") { - var urlParts = properties.modelURL.split('/'); - var propsFilename = urlParts[urlParts.length - 1]; + let urlParts = properties.modelURL.split('/'); + let propsFilename = urlParts[urlParts.length - 1]; if (propsFilename === IMAGE_MODEL_NAME) { properties.type = "Image"; } } - + // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; + showGroupsForType(properties.type); + + for (let propertyName in elPropertyElements) { + let elProperty = elPropertyElements[propertyName]; + + let propertyValue; + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + if (properties[propertyGroupName] === undefined || properties[propertyGroupName][subPropertyName] === undefined) { + continue; + } + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + propertyValue = properties[propertyGroupName][subPropertyName][subSubPropertyName]; + } else { + propertyValue = properties[propertyGroupName][subPropertyName]; + } + } else { + propertyValue = properties[propertyName]; + } - elType.innerHTML = properties.type; - elTypeIcon.innerHTML = ICON_FOR_TYPE[properties.type]; - elTypeIcon.style.display = "inline-block"; - - elLocked.checked = properties.locked; - - elName.value = properties.name; - - elVisible.checked = properties.visible; - - elPositionX.value = properties.position.x.toFixed(4); - elPositionY.value = properties.position.y.toFixed(4); - elPositionZ.value = properties.position.z.toFixed(4); - - elDimensionsX.value = properties.dimensions.x.toFixed(4); - elDimensionsY.value = properties.dimensions.y.toFixed(4); - elDimensionsZ.value = properties.dimensions.z.toFixed(4); - - elParentID.value = properties.parentID; - elParentJointIndex.value = properties.parentJointIndex; - - elRegistrationX.value = properties.registrationPoint.x.toFixed(4); - elRegistrationY.value = properties.registrationPoint.y.toFixed(4); - elRegistrationZ.value = properties.registrationPoint.z.toFixed(4); - - elRotationX.value = properties.rotation.x.toFixed(4); - elRotationY.value = properties.rotation.y.toFixed(4); - elRotationZ.value = properties.rotation.z.toFixed(4); - - elLinearVelocityX.value = properties.velocity.x.toFixed(4); - elLinearVelocityY.value = properties.velocity.y.toFixed(4); - elLinearVelocityZ.value = properties.velocity.z.toFixed(4); - elLinearDamping.value = properties.damping.toFixed(2); - - elAngularVelocityX.value = (properties.angularVelocity.x * RADIANS_TO_DEGREES).toFixed(4); - elAngularVelocityY.value = (properties.angularVelocity.y * RADIANS_TO_DEGREES).toFixed(4); - elAngularVelocityZ.value = (properties.angularVelocity.z * RADIANS_TO_DEGREES).toFixed(4); - elAngularDamping.value = properties.angularDamping.toFixed(4); - - elRestitution.value = properties.restitution.toFixed(4); - elFriction.value = properties.friction.toFixed(4); - - elGravityX.value = properties.gravity.x.toFixed(4); - elGravityY.value = properties.gravity.y.toFixed(4); - elGravityZ.value = properties.gravity.z.toFixed(4); - - elAccelerationX.value = properties.acceleration.x.toFixed(4); - elAccelerationY.value = properties.acceleration.y.toFixed(4); - elAccelerationZ.value = properties.acceleration.z.toFixed(4); - - elDensity.value = properties.density.toFixed(4); - elCollisionless.checked = properties.collisionless; - elDynamic.checked = properties.dynamic; - - elCollideStatic.checked = properties.collidesWith.indexOf("static") > -1; - elCollideKinematic.checked = properties.collidesWith.indexOf("kinematic") > -1; - elCollideDynamic.checked = properties.collidesWith.indexOf("dynamic") > -1; - elCollideMyAvatar.checked = properties.collidesWith.indexOf("myAvatar") > -1; - elCollideOtherAvatar.checked = properties.collidesWith.indexOf("otherAvatar") > -1; - - elGrabbable.checked = properties.dynamic; - - elTriggerable.checked = false; - elIgnoreIK.checked = true; - - elCloneable.checked = properties.cloneable; - elCloneableDynamic.checked = properties.cloneDynamic; - elCloneableAvatarEntity.checked = properties.cloneAvatarEntity; - elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; - elCloneableLimit.value = properties.cloneLimit; - elCloneableLifetime.value = properties.cloneLifetime; - - var grabbablesSet = false; - var parsedUserData = {}; - try { - parsedUserData = JSON.parse(properties.userData); - - if ("grabbableKey" in parsedUserData) { - grabbablesSet = true; - var grabbableData = parsedUserData.grabbableKey; - if ("grabbable" in grabbableData) { - elGrabbable.checked = grabbableData.grabbable; - } else { - elGrabbable.checked = true; - } - if ("triggerable" in grabbableData) { - elTriggerable.checked = grabbableData.triggerable; - } else if ("wantsTrigger" in grabbableData) { - elTriggerable.checked = grabbableData.wantsTrigger; - } else { - elTriggerable.checked = false; - } - if ("ignoreIK" in grabbableData) { - elIgnoreIK.checked = grabbableData.ignoreIK; - } else { - elIgnoreIK.checked = true; - } - } - } catch (e) { - // TODO: What should go here? - } - if (!grabbablesSet) { - elGrabbable.checked = true; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - elCloneable.checked = false; - } - - elCollisionSoundURL.value = properties.collisionSoundURL; - elLifetime.value = properties.lifetime; - elScriptURL.value = properties.script; - elScriptTimestamp.value = properties.scriptTimestamp; - elServerScripts.value = properties.serverScripts; - - var json = null; - try { - json = JSON.parse(properties.userData); - } catch (e) { - // normal text - deleteJSONEditor(); - elUserData.value = properties.userData; - showUserDataTextArea(); - showNewJSONEditorButton(); - hideSaveUserDataButton(); - } - if (json !== null) { - if (editor === null) { - createJSONEditor(); - } - - setEditorJSON(json); - showSaveUserDataButton(); - hideUserDataTextArea(); - hideNewJSONEditorButton(); - } - - var materialJson = null; - try { - materialJson = JSON.parse(properties.materialData); - } catch (e) { - // normal text - deleteJSONMaterialEditor(); - elMaterialData.value = properties.materialData; - showMaterialDataTextArea(); - showNewJSONMaterialEditorButton(); - hideSaveMaterialDataButton(); - } - if (materialJson !== null) { - if (materialEditor === null) { - createJSONMaterialEditor(); - } - - setMaterialEditorJSON(materialJson); - showSaveMaterialDataButton(); - hideMaterialDataTextArea(); - hideNewJSONMaterialEditorButton(); - } - - elHyperlinkHref.value = properties.href; - elDescription.value = properties.description; - - - if (properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - elShape.value = properties.shape; - setDropdownText(elShape); - } - - if (properties.type === "Shape" || properties.type === "Box" || - properties.type === "Sphere" || properties.type === "ParticleEffect") { - elColorRed.value = properties.color.red; - elColorGreen.value = properties.color.green; - elColorBlue.value = properties.color.blue; - elColorControlVariant2.style.backgroundColor = "rgb(" + properties.color.red + "," + - properties.color.green + "," + properties.color.blue + ")"; - } - - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - - elCanCastShadow.checked = properties.canCastShadow; - } - - if (properties.type === "Model") { - elModelURL.value = properties.modelURL; - elShapeType.value = properties.shapeType; - setDropdownText(elShapeType); - elCompoundShapeURL.value = properties.compoundShapeURL; - elModelAnimationURL.value = properties.animation.url; - elModelAnimationPlaying.checked = properties.animation.running; - elModelAnimationFPS.value = properties.animation.fps; - elModelAnimationFrame.value = properties.animation.currentFrame; - elModelAnimationFirstFrame.value = properties.animation.firstFrame; - elModelAnimationLastFrame.value = properties.animation.lastFrame; - elModelAnimationLoop.checked = properties.animation.loop; - elModelAnimationHold.checked = properties.animation.hold; - elModelAnimationAllowTranslation.checked = properties.animation.allowTranslation; - elModelTextures.value = properties.textures; - setTextareaScrolling(elModelTextures); - elModelOriginalTextures.value = properties.originalTextures; - setTextareaScrolling(elModelOriginalTextures); - } else if (properties.type === "Web") { - elWebSourceURL.value = properties.sourceUrl; - elWebDPI.value = properties.dpi; - } else if (properties.type === "Image") { + // workaround for shape Color & Light Color property fields sharing same property value "color" + if (propertyValue === undefined && propertyName === "lightColor") { + propertyValue = properties["color"] + } + + let isSubProperty = subProperties.indexOf(propertyName) > -1; + if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { + continue; + } + + if (elProperty instanceof Array) { + if (elProperty[1].getAttribute("type") === "number") { // vectors + if (elProperty.length === 2 || elProperty.length === 3) { + // vec2/vec3 are array of 2/3 input numbers + elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); + elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + if (elProperty[2] !== undefined) { + elProperty[2].value = (propertyValue.z * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + } + } else if (elProperty.length === 4) { + // color is array of color picker and 3 input numbers + elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; + elProperty[1].value = propertyValue.red; + elProperty[2].value = propertyValue.green; + elProperty[3].value = propertyValue.blue; + } + } else if (elProperty[0].nodeName === "SPAN") { // icons + // icon is array of elSpan (icon glyph) and elLabel + elProperty[0].innerHTML = icons[propertyName][propertyValue]; + elProperty[0].style.display = "inline-block"; + elProperty[1].innerHTML = propertyValue; //+ " (" + data.selections.length + ")"; + } + } else if (elProperty.getAttribute("subPropertyOf")) { + let propertyValue = properties[elProperty.getAttribute("subPropertyOf")]; + let subProperties = propertyValue.split(","); + elProperty.checked = subProperties.indexOf(propertyName) > -1; + } else if (elProperty.getAttribute("type") === "number") { + let fixedDecimals = elProperty.getAttribute("fixedDecimals"); + elProperty.value = propertyValue.toFixed(fixedDecimals); + } else if (elProperty.getAttribute("type") === "checkbox") { + let inverse = elProperty.getAttribute("inverse") === "true"; + elProperty.checked = inverse ? !propertyValue : propertyValue; + } else if (elProperty.nodeName === "TEXTAREA") { + elProperty.value = propertyValue; + setTextareaScrolling(elProperty); + } else if (elProperty.nodeName === "SELECT") { // dropdown + elProperty.value = propertyValue; + } else { + elProperty.value = propertyValue; + } + + let propertyShowRules = showPropertyRules[propertyName]; + if (propertyShowRules !== undefined) { + for (let propertyToShow in propertyShowRules) { + let showIfThisPropertyValue = propertyShowRules[propertyToShow]; + let show = String(propertyValue) === String(showIfThisPropertyValue); + let elPropertyToShow = elPropertyElements[propertyToShow]; + if (elPropertyToShow) { + let parentNode = elPropertyToShow.parentNode; + if (parentNode === undefined && elPropertyToShow instanceof Array) { + parentNode = elPropertyToShow[0].parentNode; + } + parentNode.style.display = show ? "block" : "none"; + } + } + } + } + + let elGrabbable = elPropertyElements["grabbable"]; + let elTriggerable = elPropertyElements["triggerable"]; + let elIgnoreIK = elPropertyElements["ignoreIK"]; + elGrabbable.checked = elPropertyElements["dynamic"].checked; + elTriggerable.checked = false; + elIgnoreIK.checked = true; + let grabbablesSet = false; + let parsedUserData = {}; + try { + parsedUserData = JSON.parse(properties.userData); + if ("grabbableKey" in parsedUserData) { + grabbablesSet = true; + let grabbableData = parsedUserData.grabbableKey; + if ("grabbable" in grabbableData) { + elGrabbable.checked = grabbableData.grabbable; + } else { + elGrabbable.checked = true; + } + if ("triggerable" in grabbableData) { + elTriggerable.checked = grabbableData.triggerable; + } else if ("wantsTrigger" in grabbableData) { + elTriggerable.checked = grabbableData.wantsTrigger; + } else { + elTriggerable.checked = false; + } + if ("ignoreIK" in grabbableData) { + elIgnoreIK.checked = grabbableData.ignoreIK; + } else { + elIgnoreIK.checked = true; + } + } + } catch (e) { + // TODO: What should go here? + } + if (!grabbablesSet) { + elGrabbable.checked = true; + elTriggerable.checked = false; + elIgnoreIK.checked = true; + } + + if (properties.type === "Image") { var imageLink = JSON.parse(properties.textures)["tex.picture"]; - elImageURL.value = imageLink; - } else if (properties.type === "Text") { - elTextText.value = properties.text; - elTextLineHeight.value = properties.lineHeight.toFixed(4); - elTextFaceCamera.checked = properties.faceCamera; - elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + - properties.textColor.green + "," + - properties.textColor.blue + ")"; - elTextTextColorRed.value = properties.textColor.red; - elTextTextColorGreen.value = properties.textColor.green; - elTextTextColorBlue.value = properties.textColor.blue; - elTextBackgroundColor.style.backgroundColor = "rgb(" + properties.backgroundColor.red + "," + - properties.backgroundColor.green + "," + - properties.backgroundColor.blue + ")"; - elTextBackgroundColorRed.value = properties.backgroundColor.red; - elTextBackgroundColorGreen.value = properties.backgroundColor.green; - elTextBackgroundColorBlue.value = properties.backgroundColor.blue; - } else if (properties.type === "Light") { - elLightSpotLight.checked = properties.isSpotlight; - - elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + - properties.color.green + "," + properties.color.blue + ")"; - elLightColorRed.value = properties.color.red; - elLightColorGreen.value = properties.color.green; - elLightColorBlue.value = properties.color.blue; - - elLightIntensity.value = properties.intensity.toFixed(1); - elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); - elLightExponent.value = properties.exponent.toFixed(2); - elLightCutoff.value = properties.cutoff.toFixed(2); - } else if (properties.type === "Zone") { - // Key light - elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); - elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); - elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); - - elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + - properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; - elZoneKeyLightColorRed.value = properties.keyLight.color.red; - elZoneKeyLightColorGreen.value = properties.keyLight.color.green; - elZoneKeyLightColorBlue.value = properties.keyLight.color.blue; - elZoneKeyLightIntensity.value = properties.keyLight.intensity.toFixed(2); - elZoneKeyLightDirectionX.value = properties.keyLight.direction.x.toFixed(2); - elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); - - elZoneKeyLightCastShadows.checked = properties.keyLight.castShadows; - - // Skybox - elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); - elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); - elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); - - // Ambient light - elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); - elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); - elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); - - elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); - elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; - - // Haze - elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); - elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); - elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); - - elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); - elZoneHazeColor.style.backgroundColor = "rgb(" + - properties.haze.hazeColor.red + "," + - properties.haze.hazeColor.green + "," + - properties.haze.hazeColor.blue + ")"; - - elZoneHazeColorRed.value = properties.haze.hazeColor.red; - elZoneHazeColorGreen.value = properties.haze.hazeColor.green; - elZoneHazeColorBlue.value = properties.haze.hazeColor.blue; - elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2); - - elZoneHazeGlareColor.style.backgroundColor = "rgb(" + - properties.haze.hazeGlareColor.red + "," + - properties.haze.hazeGlareColor.green + "," + - properties.haze.hazeGlareColor.blue + ")"; - - elZoneHazeGlareColorRed.value = properties.haze.hazeGlareColor.red; - elZoneHazeGlareColorGreen.value = properties.haze.hazeGlareColor.green; - elZoneHazeGlareColorBlue.value = properties.haze.hazeGlareColor.blue; - - elZoneHazeEnableGlare.checked = properties.haze.hazeEnableGlare; - elZoneHazeGlareAngle.value = properties.haze.hazeGlareAngle.toFixed(0); - - elZoneHazeAltitudeEffect.checked = properties.haze.hazeAltitudeEffect; - elZoneHazeBaseRef.value = properties.haze.hazeBaseRef.toFixed(0); - elZoneHazeCeiling.value = properties.haze.hazeCeiling.toFixed(0); - - elZoneBloomModeInherit.checked = (properties.bloomMode === 'inherit'); - elZoneBloomModeDisabled.checked = (properties.bloomMode === 'disabled'); - elZoneBloomModeEnabled.checked = (properties.bloomMode === 'enabled'); - - elZoneBloomIntensity.value = properties.bloom.bloomIntensity.toFixed(2); - elZoneBloomThreshold.value = properties.bloom.bloomThreshold.toFixed(2); - elZoneBloomSize.value = properties.bloom.bloomSize.toFixed(2); - - elShapeType.value = properties.shapeType; - elCompoundShapeURL.value = properties.compoundShapeURL; - - elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," + - properties.skybox.color.green + "," + properties.skybox.color.blue + ")"; - elZoneSkyboxColorRed.value = properties.skybox.color.red; - elZoneSkyboxColorGreen.value = properties.skybox.color.green; - elZoneSkyboxColorBlue.value = properties.skybox.color.blue; - elZoneSkyboxURL.value = properties.skybox.url; - - elZoneFlyingAllowed.checked = properties.flyingAllowed; - elZoneGhostingAllowed.checked = properties.ghostingAllowed; - elZoneFilterURL.value = properties.filterURL; - - // Show/hide sections as required - showElements(document.getElementsByClassName('skybox-section'), - elZoneSkyboxModeEnabled.checked); - - showElements(document.getElementsByClassName('keylight-section'), - elZoneKeyLightModeEnabled.checked); - - showElements(document.getElementsByClassName('ambient-section'), - elZoneAmbientLightModeEnabled.checked); - - showElements(document.getElementsByClassName('haze-section'), - elZoneHazeModeEnabled.checked); - - showElements(document.getElementsByClassName('bloom-section'), - elZoneBloomModeEnabled.checked); - } else if (properties.type === "PolyVox") { - elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); - elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); - elVoxelVolumeSizeZ.value = properties.voxelVolumeSize.z.toFixed(2); - elVoxelSurfaceStyle.value = properties.voxelSurfaceStyle; - setDropdownText(elVoxelSurfaceStyle); - elXTextureURL.value = properties.xTextureURL; - elYTextureURL.value = properties.yTextureURL; - elZTextureURL.value = properties.zTextureURL; - } else if (properties.type === "Material") { - elMaterialURL.value = properties.materialURL; - //elMaterialMappingMode.value = properties.materialMappingMode; - //setDropdownText(elMaterialMappingMode); - elPriority.value = properties.priority; - if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { + elPropertyElements["image"].value = imageLink; + } else if (properties.type === "Material") { + let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; + let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; + let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; + if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { elParentMaterialNameString.value = properties.parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = false; @@ -1551,32 +2356,64 @@ function loaded() { showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = true; } - elMaterialMappingPosX.value = properties.materialMappingPos.x.toFixed(4); - elMaterialMappingPosY.value = properties.materialMappingPos.y.toFixed(4); - elMaterialMappingScaleX.value = properties.materialMappingScale.x.toFixed(4); - elMaterialMappingScaleY.value = properties.materialMappingScale.y.toFixed(4); - elMaterialMappingRot.value = properties.materialMappingRot.toFixed(2); + } + + let json = null; + try { + json = JSON.parse(properties.userData); + } catch (e) { + // normal text + deleteJSONEditor(); + elPropertyElements["userData"].value = properties.userData; + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + hideUserDataSaved(); + } + if (json !== null) { + if (editor === null) { + createJSONEditor(); + } + setEditorJSON(json); + showSaveUserDataButton(); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + hideUserDataSaved(); } - // Only these types can cast a shadow - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - - showElements(document.getElementsByClassName('can-cast-shadow-section'), true); - } else { - showElements(document.getElementsByClassName('can-cast-shadow-section'), false); + let materialJson = null; + try { + materialJson = JSON.parse(properties.materialData); + } catch (e) { + // normal text + deleteJSONMaterialEditor(); + elPropertyElements["materialData"].value = properties.materialData; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + hideMaterialDataSaved(); } - - if (properties.locked) { - disableProperties(); - elLocked.removeAttribute('disabled'); - } else { - enableProperties(); - elSaveUserData.disabled = true; - elSaveMaterialData.disabled = true; + if (materialJson !== null) { + if (materialEditor === null) { + createJSONMaterialEditor(); + } + setMaterialEditorJSON(materialJson); + showSaveMaterialDataButton(); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + hideMaterialDataSaved(); } - - var activeElement = document.activeElement; + + if (properties.locked) { + disableProperties(); + elPropertyElements["locked"].removeAttribute('disabled'); + } else { + enableProperties(); + disableSaveUserDataButton(); + disableSaveMaterialDataButton() + } + + var activeElement = document.activeElement; if (doSelectElement && typeof activeElement.select !== "undefined") { activeElement.select(); } @@ -1584,270 +2421,79 @@ function loaded() { } }); } - - elLocked.addEventListener('change', createEmitCheckedPropertyUpdateFunction('locked')); - elName.addEventListener('change', createEmitTextPropertyUpdateFunction('name')); - elHyperlinkHref.addEventListener('change', createEmitTextPropertyUpdateFunction('href')); - elDescription.addEventListener('change', createEmitTextPropertyUpdateFunction('description')); - elVisible.addEventListener('change', createEmitCheckedPropertyUpdateFunction('visible')); - - var positionChangeFunction = createEmitVec3PropertyUpdateFunction( - 'position', elPositionX, elPositionY, elPositionZ); - elPositionX.addEventListener('change', positionChangeFunction); - elPositionY.addEventListener('change', positionChangeFunction); - elPositionZ.addEventListener('change', positionChangeFunction); - - var dimensionsChangeFunction = createEmitVec3PropertyUpdateFunction( - 'dimensions', elDimensionsX, elDimensionsY, elDimensionsZ); - elDimensionsX.addEventListener('change', dimensionsChangeFunction); - elDimensionsY.addEventListener('change', dimensionsChangeFunction); - elDimensionsZ.addEventListener('change', dimensionsChangeFunction); - - elParentID.addEventListener('change', createEmitTextPropertyUpdateFunction('parentID')); - elParentJointIndex.addEventListener('change', createEmitNumberPropertyUpdateFunction('parentJointIndex', 0)); - - var registrationChangeFunction = createEmitVec3PropertyUpdateFunction( - 'registrationPoint', elRegistrationX, elRegistrationY, elRegistrationZ); - elRegistrationX.addEventListener('change', registrationChangeFunction); - elRegistrationY.addEventListener('change', registrationChangeFunction); - elRegistrationZ.addEventListener('change', registrationChangeFunction); - - var rotationChangeFunction = createEmitVec3PropertyUpdateFunction( - 'rotation', elRotationX, elRotationY, elRotationZ); - elRotationX.addEventListener('change', rotationChangeFunction); - elRotationY.addEventListener('change', rotationChangeFunction); - elRotationZ.addEventListener('change', rotationChangeFunction); - - var velocityChangeFunction = createEmitVec3PropertyUpdateFunction( - 'velocity', elLinearVelocityX, elLinearVelocityY, elLinearVelocityZ); - elLinearVelocityX.addEventListener('change', velocityChangeFunction); - elLinearVelocityY.addEventListener('change', velocityChangeFunction); - elLinearVelocityZ.addEventListener('change', velocityChangeFunction); - elLinearDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('damping')); - - var angularVelocityChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier( - 'angularVelocity', elAngularVelocityX, elAngularVelocityY, elAngularVelocityZ, DEGREES_TO_RADIANS); - elAngularVelocityX.addEventListener('change', angularVelocityChangeFunction); - elAngularVelocityY.addEventListener('change', angularVelocityChangeFunction); - elAngularVelocityZ.addEventListener('change', angularVelocityChangeFunction); - elAngularDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('angularDamping')); - - elRestitution.addEventListener('change', createEmitNumberPropertyUpdateFunction('restitution')); - elFriction.addEventListener('change', createEmitNumberPropertyUpdateFunction('friction')); - - var gravityChangeFunction = createEmitVec3PropertyUpdateFunction( - 'gravity', elGravityX, elGravityY, elGravityZ); - elGravityX.addEventListener('change', gravityChangeFunction); - elGravityY.addEventListener('change', gravityChangeFunction); - elGravityZ.addEventListener('change', gravityChangeFunction); - - var accelerationChangeFunction = createEmitVec3PropertyUpdateFunction( - 'acceleration', elAccelerationX, elAccelerationY, elAccelerationZ); - elAccelerationX.addEventListener('change', accelerationChangeFunction); - elAccelerationY.addEventListener('change', accelerationChangeFunction); - elAccelerationZ.addEventListener('change', accelerationChangeFunction); - - elDensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('density')); - elCollisionless.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionless')); - elDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('dynamic')); - - elCollideDynamic.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideDynamic, 'dynamic'); - }); - - elCollideKinematic.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideKinematic, 'kinematic'); - }); - - elCollideStatic.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideStatic, 'static'); - }); - elCollideMyAvatar.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideMyAvatar, 'myAvatar'); - }); - elCollideOtherAvatar.addEventListener('change', function() { - updateCheckedSubProperty("collidesWith", properties.collidesWith, elCollideOtherAvatar, 'otherAvatar'); - }); - - elGrabbable.addEventListener('change', function() { - if (elCloneable.checked) { - elGrabbable.checked = false; - } + + // Server Script Status + let elServerScript = elPropertyElements["serverScripts"]; + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "property"); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", "property-serverScripts-status"); + elLabel.innerText = "Server Script Status"; + let elServerScriptStatus = document.createElement('span'); + elServerScriptStatus.setAttribute("id", "property-serverScripts-status"); + elDiv.appendChild(elLabel); + elDiv.appendChild(elServerScriptStatus); + elServerScript.parentNode.appendChild(elDiv); + + // Server Script Error + elDiv = document.createElement('div'); + elDiv.setAttribute("class", "property"); + let elServerScriptError = document.createElement('textarea'); + elServerScriptError.setAttribute("id", "property-serverScripts-error"); + elDiv.appendChild(elServerScriptError); + elServerScript.parentNode.appendChild(elDiv); + + let elScript = elPropertyElements["script"]; + elScript.parentNode.setAttribute("class", "property url refresh"); + elServerScript.parentNode.setAttribute("class", "property url refresh"); + + // User Data + let elUserData = elPropertyElements["userData"]; + elDiv = elUserData.parentNode; + let elStaticUserData = document.createElement('div'); + elStaticUserData.setAttribute("id", "property-userData-static"); + let elUserDataEditor = document.createElement('div'); + elUserDataEditor.setAttribute("id", "property-userData-editor"); + let elUserDataSaved = document.createElement('span'); + elUserDataSaved.setAttribute("id", "property-userData-saved"); + elUserDataSaved.innerText = "Saved!"; + elDiv.childNodes[2].appendChild(elUserDataSaved); + elDiv.insertBefore(elStaticUserData, elUserData); + elDiv.insertBefore(elUserDataEditor, elUserData); + + // Material Data + let elMaterialData = elPropertyElements["materialData"]; + elDiv = elMaterialData.parentNode; + let elStaticMaterialData = document.createElement('div'); + elStaticMaterialData.setAttribute("id", "property-materialData-static"); + let elMaterialDataEditor = document.createElement('div'); + elMaterialDataEditor.setAttribute("id", "property-materialData-editor"); + let elMaterialDataSaved = document.createElement('span'); + elMaterialDataSaved.setAttribute("id", "property-materialData-saved"); + elMaterialDataSaved.innerText = "Saved!"; + elDiv.childNodes[2].appendChild(elMaterialDataSaved); + elDiv.insertBefore(elStaticMaterialData, elMaterialData); + elDiv.insertBefore(elMaterialDataEditor, elMaterialData); + + // User Data Fields + let elGrabbable = elPropertyElements["grabbable"]; + let elTriggerable = elPropertyElements["triggerable"]; + let elIgnoreIK = elPropertyElements["ignoreIK"]; + elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); }); - - elCloneable.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneable')); - elCloneableDynamic.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneDynamic')); - elCloneableAvatarEntity.addEventListener('change', createEmitCheckedPropertyUpdateFunction('cloneAvatarEntity')); - elCloneableLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLifetime')); - elCloneableLimit.addEventListener('change', createEmitNumberPropertyUpdateFunction('cloneLimit')); - - elTriggerable.addEventListener('change', function() { + elTriggerable.addEventListener('change', function() { userDataChanger("grabbableKey", "triggerable", elTriggerable, elUserData, false, ['wantsTrigger']); }); elIgnoreIK.addEventListener('change', function() { userDataChanger("grabbableKey", "ignoreIK", elIgnoreIK, elUserData, true); }); - - elCollisionSoundURL.addEventListener('change', createEmitTextPropertyUpdateFunction('collisionSoundURL')); - - elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime')); - elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script')); - elScriptTimestamp.addEventListener('change', createEmitNumberPropertyUpdateFunction('scriptTimestamp')); - elServerScripts.addEventListener('change', createEmitTextPropertyUpdateFunction('serverScripts')); - elServerScripts.addEventListener('change', function() { - // invalidate the current status (so that same-same updates can still be observed visually) - elServerScriptStatus.innerText = PENDING_SCRIPT_STATUS; - }); - - elClearUserData.addEventListener("click", function() { - deleteJSONEditor(); - elUserData.value = ""; - showUserDataTextArea(); - showNewJSONEditorButton(); - hideSaveUserDataButton(); - updateProperty('userData', elUserData.value); - }); - - elSaveUserData.addEventListener("click", function() { - saveJSONUserData(true); - }); - - elUserData.addEventListener('change', createEmitTextPropertyUpdateFunction('userData')); - - elNewJSONEditor.addEventListener('click', function() { - deleteJSONEditor(); - createJSONEditor(); - var data = {}; - setEditorJSON(data); - hideUserDataTextArea(); - hideNewJSONEditorButton(); - showSaveUserDataButton(); - }); - - elClearMaterialData.addEventListener("click", function() { - deleteJSONMaterialEditor(); - elMaterialData.value = ""; - showMaterialDataTextArea(); - showNewJSONMaterialEditorButton(); - hideSaveMaterialDataButton(); - updateProperty('materialData', elMaterialData.value); - }); - - elSaveMaterialData.addEventListener("click", function() { - saveJSONMaterialData(true); - }); - - elMaterialData.addEventListener('change', createEmitTextPropertyUpdateFunction('materialData')); - - elNewJSONMaterialEditor.addEventListener('click', function() { - deleteJSONMaterialEditor(); - createJSONMaterialEditor(); - var data = {}; - setMaterialEditorJSON(data); - hideMaterialDataTextArea(); - hideNewJSONMaterialEditorButton(); - showSaveMaterialDataButton(); - }); - - var colorChangeFunction = createEmitColorPropertyUpdateFunction( - 'color', elColorRed, elColorGreen, elColorBlue); - elColorRed.addEventListener('change', colorChangeFunction); - elColorGreen.addEventListener('change', colorChangeFunction); - elColorBlue.addEventListener('change', colorChangeFunction); - colorPickers['#property-color-control2'] = $('#property-color-control2').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-color-control2').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-color-control2'].colpickSetColor({ - "r": elColorRed.value, - "g": elColorGreen.value, - "b": elColorBlue.value}); - }, - onHide: function(colpick) { - $('#property-color-control2').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elLightSpotLight.addEventListener('change', createEmitCheckedPropertyUpdateFunction('isSpotlight')); - - var lightColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'color', elLightColorRed, elLightColorGreen, elLightColorBlue); - elLightColorRed.addEventListener('change', lightColorChangeFunction); - elLightColorGreen.addEventListener('change', lightColorChangeFunction); - elLightColorBlue.addEventListener('change', lightColorChangeFunction); - colorPickers['#property-light-color'] = $('#property-light-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-light-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-light-color'].colpickSetColor({ - "r": elLightColorRed.value, - "g": elLightColorGreen.value, - "b": elLightColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-light-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elLightIntensity.addEventListener('change', createEmitNumberPropertyUpdateFunction('intensity', 1)); - elLightFalloffRadius.addEventListener('change', createEmitNumberPropertyUpdateFunction('falloffRadius', 1)); - elLightExponent.addEventListener('change', createEmitNumberPropertyUpdateFunction('exponent', 2)); - elLightCutoff.addEventListener('change', createEmitNumberPropertyUpdateFunction('cutoff', 2)); - - elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); - - elCanCastShadow.addEventListener('change', createEmitCheckedPropertyUpdateFunction('canCastShadow')); - - elImageURL.addEventListener('change', createImageURLUpdateFunction('textures')); - - elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); - elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi', 0)); - - elModelURL.addEventListener('change', createEmitTextPropertyUpdateFunction('modelURL')); - elShapeType.addEventListener('change', createEmitTextPropertyUpdateFunction('shapeType')); - elCompoundShapeURL.addEventListener('change', createEmitTextPropertyUpdateFunction('compoundShapeURL')); - - elModelAnimationURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('animation', 'url')); - elModelAnimationPlaying.addEventListener('change',createEmitGroupCheckedPropertyUpdateFunction('animation', 'running')); - elModelAnimationFPS.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'fps')); - elModelAnimationFrame.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('animation', 'currentFrame')); - elModelAnimationFirstFrame.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('animation', 'firstFrame')); - elModelAnimationLastFrame.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame')); - elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop')); - elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold')); - elModelAnimationAllowTranslation.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation')); - - elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); - - elMaterialURL.addEventListener('change', createEmitTextPropertyUpdateFunction('materialURL')); - //elMaterialMappingMode.addEventListener('change', createEmitTextPropertyUpdateFunction('materialMappingMode')); - elPriority.addEventListener('change', createEmitNumberPropertyUpdateFunction('priority', 0)); - - elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); + + // Special Property Callbacks + let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; + let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; + let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; + elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); elParentMaterialNameNumber.addEventListener('change', function () { updateProperty("parentMaterialName", this.value); }); elParentMaterialNameCheckbox.addEventListener('change', function () { if (this.checked) { @@ -1858,339 +2504,43 @@ function loaded() { showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); } }); + + elPropertyElements["image"].addEventListener('change', createImageURLUpdateFunction('textures')); + + // Collapsible sections + let elCollapsible = document.getElementsByClassName("section-header"); - var materialMappingPosChangeFunction = createEmitVec2PropertyUpdateFunction('materialMappingPos', elMaterialMappingPosX, elMaterialMappingPosY); - elMaterialMappingPosX.addEventListener('change', materialMappingPosChangeFunction); - elMaterialMappingPosY.addEventListener('change', materialMappingPosChangeFunction); - var materialMappingScaleChangeFunction = createEmitVec2PropertyUpdateFunction('materialMappingScale', elMaterialMappingScaleX, elMaterialMappingScaleY); - elMaterialMappingScaleX.addEventListener('change', materialMappingScaleChangeFunction); - elMaterialMappingScaleY.addEventListener('change', materialMappingScaleChangeFunction); - elMaterialMappingRot.addEventListener('change', createEmitNumberPropertyUpdateFunction('materialMappingRot', 2)); + let toggleCollapsedEvent = function(event) { + let element = event.target.parentNode.parentNode; + let isCollapsed = element.dataset.collapsed !== "true"; + element.dataset.collapsed = isCollapsed ? "true" : false; + element.setAttribute("collapsed", isCollapsed ? "true" : "false"); + element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M"; + }; - elTextText.addEventListener('change', createEmitTextPropertyUpdateFunction('text')); - elTextFaceCamera.addEventListener('change', createEmitCheckedPropertyUpdateFunction('faceCamera')); - elTextLineHeight.addEventListener('change', createEmitNumberPropertyUpdateFunction('lineHeight')); - var textTextColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'textColor', elTextTextColorRed, elTextTextColorGreen, elTextTextColorBlue); - elTextTextColorRed.addEventListener('change', textTextColorChangeFunction); - elTextTextColorGreen.addEventListener('change', textTextColorChangeFunction); - elTextTextColorBlue.addEventListener('change', textTextColorChangeFunction); - colorPickers['#property-text-text-color'] = $('#property-text-text-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-text-text-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-text-text-color'].colpickSetColor({ - "r": elTextTextColorRed.value, - "g": elTextTextColorGreen.value, - "b": elTextTextColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-text-text-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).attr('active', 'false'); - emitColorPropertyUpdate('textColor', rgb.r, rgb.g, rgb.b); - } - }); + for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { + let curCollapsibleElement = elCollapsible[collapseIndex]; + curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); + } + + // Textarea scrollbars + let elTextareas = document.getElementsByTagName("TEXTAREA"); - var textBackgroundColorChangeFunction = createEmitColorPropertyUpdateFunction( - 'backgroundColor', elTextBackgroundColorRed, elTextBackgroundColorGreen, elTextBackgroundColorBlue); + let textareaOnChangeEvent = function(event) { + setTextareaScrolling(event.target); + }; - elTextBackgroundColorRed.addEventListener('change', textBackgroundColorChangeFunction); - elTextBackgroundColorGreen.addEventListener('change', textBackgroundColorChangeFunction); - elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction); - colorPickers['#property-text-background-color'] = $('#property-text-background-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-text-background-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-text-background-color'].colpickSetColor({ - "r": elTextBackgroundColorRed.value, - "g": elTextBackgroundColorGreen.value, - "b": elTextBackgroundColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-text-background-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('backgroundColor', rgb.r, rgb.g, rgb.b); - } - }); - - // Key light - var keyLightModeChanged = createZoneComponentModeChangedFunction('keyLightMode', - elZoneKeyLightModeInherit, elZoneKeyLightModeDisabled, elZoneKeyLightModeEnabled); - - elZoneKeyLightModeInherit.addEventListener('change', keyLightModeChanged); - elZoneKeyLightModeDisabled.addEventListener('change', keyLightModeChanged); - elZoneKeyLightModeEnabled.addEventListener('change', keyLightModeChanged); - - colorPickers['#property-zone-key-light-color'] = $('#property-zone-key-light-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-key-light-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-key-light-color'].colpickSetColor({ - "r": elZoneKeyLightColorRed.value, - "g": elZoneKeyLightColorGreen.value, - "b": elZoneKeyLightColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-key-light-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); - } - }); - var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', - elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); - - elZoneKeyLightColorRed.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightColorGreen.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightIntensity.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); - - var zoneKeyLightDirectionChangeFunction = createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', - elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); - - elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); - elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); - - elZoneKeyLightCastShadows.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('keyLight', 'castShadows')); - - // Skybox - var skyboxModeChanged = createZoneComponentModeChangedFunction('skyboxMode', - elZoneSkyboxModeInherit, elZoneSkyboxModeDisabled, elZoneSkyboxModeEnabled); - - elZoneSkyboxModeInherit.addEventListener('change', skyboxModeChanged); - elZoneSkyboxModeDisabled.addEventListener('change', skyboxModeChanged); - elZoneSkyboxModeEnabled.addEventListener('change', skyboxModeChanged); - - // Ambient light - elCopySkyboxURLToAmbientURL.addEventListener("click", function () { - document.getElementById("property-zone-key-ambient-url").value = properties.skybox.url; - properties.ambientLight.ambientURL = properties.skybox.url; - updateProperties(properties); - }); - - var ambientLightModeChanged = createZoneComponentModeChangedFunction('ambientLightMode', - elZoneAmbientLightModeInherit, elZoneAmbientLightModeDisabled, elZoneAmbientLightModeEnabled); - - elZoneAmbientLightModeInherit.addEventListener('change', ambientLightModeChanged); - elZoneAmbientLightModeDisabled.addEventListener('change', ambientLightModeChanged); - elZoneAmbientLightModeEnabled.addEventListener('change', ambientLightModeChanged); - - elZoneAmbientLightIntensity.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('ambientLight', 'ambientIntensity')); - - elZoneAmbientLightURL.addEventListener('change', - createEmitGroupTextPropertyUpdateFunction('ambientLight', 'ambientURL')); - - // Haze - var hazeModeChanged = createZoneComponentModeChangedFunction('hazeMode', - elZoneHazeModeInherit, elZoneHazeModeDisabled, elZoneHazeModeEnabled); - - elZoneHazeModeInherit.addEventListener('change', hazeModeChanged); - elZoneHazeModeDisabled.addEventListener('change', hazeModeChanged); - elZoneHazeModeEnabled.addEventListener('change', hazeModeChanged); - - elZoneHazeRange.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeRange')); - - colorPickers['#property-zone-haze-color'] = $('#property-zone-haze-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-haze-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-haze-color'].colpickSetColor({ - "r": elZoneHazeColorRed.value, - "g": elZoneHazeColorGreen.value, - "b": elZoneHazeColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-haze-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('hazeColor', rgb.r, rgb.g, rgb.b, 'haze'); - } - }); - var zoneHazeColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('haze', 'hazeColor', - elZoneHazeColorRed, - elZoneHazeColorGreen, - elZoneHazeColorBlue); - - elZoneHazeColorRed.addEventListener('change', zoneHazeColorChangeFunction); - elZoneHazeColorGreen.addEventListener('change', zoneHazeColorChangeFunction); - elZoneHazeColorBlue.addEventListener('change', zoneHazeColorChangeFunction); - - colorPickers['#property-zone-haze-glare-color'] = $('#property-zone-haze-glare-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-haze-glare-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-haze-glare-color'].colpickSetColor({ - "r": elZoneHazeGlareColorRed.value, - "g": elZoneHazeGlareColorGreen.value, - "b": elZoneHazeGlareColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-haze-glare-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('hazeGlareColor', rgb.r, rgb.g, rgb.b, 'haze'); - } - }); - var zoneHazeGlareColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('haze', 'hazeGlareColor', - elZoneHazeGlareColorRed, - elZoneHazeGlareColorGreen, - elZoneHazeGlareColorBlue); - - elZoneHazeGlareColorRed.addEventListener('change', zoneHazeGlareColorChangeFunction); - elZoneHazeGlareColorGreen.addEventListener('change', zoneHazeGlareColorChangeFunction); - elZoneHazeGlareColorBlue.addEventListener('change', zoneHazeGlareColorChangeFunction); - - elZoneHazeEnableGlare.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); - elZoneHazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); - - elZoneHazeAltitudeEffect.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); - elZoneHazeCeiling.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeCeiling')); - elZoneHazeBaseRef.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBaseRef')); - - elZoneHazeBackgroundBlend.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend')); - - // Bloom - var bloomModeChanged = createZoneComponentModeChangedFunction('bloomMode', - elZoneBloomModeInherit, elZoneBloomModeDisabled, elZoneBloomModeEnabled); - - elZoneBloomModeInherit.addEventListener('change', bloomModeChanged); - elZoneBloomModeDisabled.addEventListener('change', bloomModeChanged); - elZoneBloomModeEnabled.addEventListener('change', bloomModeChanged); - - elZoneBloomIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomIntensity')); - elZoneBloomThreshold.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomThreshold')); - elZoneBloomSize.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('bloom', 'bloomSize')); - - var zoneSkyboxColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('skybox', 'color', - elZoneSkyboxColorRed, elZoneSkyboxColorGreen, elZoneSkyboxColorBlue); - elZoneSkyboxColorRed.addEventListener('change', zoneSkyboxColorChangeFunction); - elZoneSkyboxColorGreen.addEventListener('change', zoneSkyboxColorChangeFunction); - elZoneSkyboxColorBlue.addEventListener('change', zoneSkyboxColorChangeFunction); - colorPickers['#property-zone-skybox-color'] = $('#property-zone-skybox-color').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $('#property-zone-skybox-color').attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers['#property-zone-skybox-color'].colpickSetColor({ - "r": elZoneSkyboxColorRed.value, - "g": elZoneSkyboxColorGreen.value, - "b": elZoneSkyboxColorBlue.value - }); - }, - onHide: function(colpick) { - $('#property-zone-skybox-color').attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'skybox'); - } - }); - - elZoneSkyboxURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('skybox', 'url')); - - elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); - elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); - elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL')); - - var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( - 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); - elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction); - elVoxelVolumeSizeY.addEventListener('change', voxelVolumeSizeChangeFunction); - elVoxelVolumeSizeZ.addEventListener('change', voxelVolumeSizeChangeFunction); - elVoxelSurfaceStyle.addEventListener('change', createEmitTextPropertyUpdateFunction('voxelSurfaceStyle')); - elXTextureURL.addEventListener('change', createEmitTextPropertyUpdateFunction('xTextureURL')); - elYTextureURL.addEventListener('change', createEmitTextPropertyUpdateFunction('yTextureURL')); - elZTextureURL.addEventListener('change', createEmitTextPropertyUpdateFunction('zTextureURL')); - - elMoveSelectionToGrid.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveSelectionToGrid" - })); - }); - elMoveAllToGrid.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveAllToGrid" - })); - }); - elResetToNaturalDimensions.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "resetToNaturalDimensions" - })); - }); - elRescaleDimensionsButton.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "rescaleDimensions", - percentage: parseFloat(elRescaleDimensionsPct.value) - })); - }); - elReloadScriptsButton.addEventListener("click", function() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadClientScripts" - })); - }); - elReloadServerScriptsButton.addEventListener("click", function() { - // invalidate the current status (so that same-same updates can still be observed visually) - elServerScriptStatus.innerText = PENDING_SCRIPT_STATUS; - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadServerScripts" - })); - }); - - document.addEventListener("keydown", function (keyDown) { + for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { + let curTextAreaElement = elTextareas[textAreaIndex]; + setTextareaScrolling(curTextAreaElement); + curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); + curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); + /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize + event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ + curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); + } + + document.addEventListener("keydown", function (keyDown) { if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { if (keyDown.shiftKey) { EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); @@ -2199,155 +2549,28 @@ function loaded() { } } }); + window.onblur = function() { // Fake a change event - var ev = document.createEvent("HTMLEvents"); + let ev = document.createEvent("HTMLEvents"); ev.initEvent("change", true, true); document.activeElement.dispatchEvent(ev); }; - - // For input and textarea elements, select all of the text on focus - var els = document.querySelectorAll("input, textarea"); - for (var i = 0; i < els.length; i++) { + + // For input and textarea elements, select all of the text on focus + let els = document.querySelectorAll("input, textarea"); + for (let i = 0; i < els.length; i++) { els[i].onfocus = function (e) { e.target.select(); }; } + + bindAllNonJSONEditorElements(); - bindAllNonJSONEditorElements(); - }); - - // Collapsible sections - var elCollapsible = document.getElementsByClassName("section-header"); - - var toggleCollapsedEvent = function(event) { - var element = event.target.parentNode.parentNode; - var isCollapsed = element.dataset.collapsed !== "true"; - element.dataset.collapsed = isCollapsed ? "true" : false; - element.setAttribute("collapsed", isCollapsed ? "true" : "false"); - element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M"; - }; - - for (var collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { - var curCollapsibleElement = elCollapsible[collapseIndex]; - curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); - } - - - // Textarea scrollbars - var elTextareas = document.getElementsByTagName("TEXTAREA"); - - var textareaOnChangeEvent = function(event) { - setTextareaScrolling(event.target); - }; - - for (var textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { - var curTextAreaElement = elTextareas[textAreaIndex]; - setTextareaScrolling(curTextAreaElement); - curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); - curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); - /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize - event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ - curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); - } - - // Dropdowns - // For each dropdown the following replacement is created in place of the original dropdown... - // Structure created: - //
- //
display textcarat
- //
- //
    - //
  • 0) { - var el = elDropdowns[0]; - el.parentNode.removeChild(el); - elDropdowns = document.getElementsByTagName("select"); - } + showGroupsForType("None"); + resetProperties(); + disableProperties(); + }); augmentSpinButtons(); From 7aba0a9082499ed98ae1359ef7b329566afaa295 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 9 Oct 2018 10:17:39 -0700 Subject: [PATCH 060/276] cleanup --- scripts/system/html/css/edit-style.css | 24 +- scripts/system/html/js/entityProperties.js | 2353 ++++++++++---------- 2 files changed, 1187 insertions(+), 1190 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index b7aac02c7c..e700b50839 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -614,6 +614,10 @@ hr { margin-top: 0; } +.checkbox-sub-props { + margin-top: 18px; +} + .property .number { float: left; } @@ -918,6 +922,14 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { outline: none; } +fieldset .checkbox-sub-props { + margin-top: 0; +} + +fieldset .checkbox-sub-props .property:first-child { + margin-top: 0; +} + .column { vertical-align: top; } @@ -1269,7 +1281,7 @@ th#entity-hasScript { #base #property-type { padding: 5px 24px 5px 0; - border-right: 1px solid #808080; + border-right: 1px solid #808080; width: auto; display: inline-block; } @@ -1277,13 +1289,13 @@ th#entity-hasScript { #base #div-locked { position: absolute; top: 0px; - right: 140px; + right: 140px; } #base #div-visible { position: absolute; top: 20px; - right: 20px; + right: 20px; } #base .checkbox { @@ -1361,9 +1373,9 @@ input#property-scale-button-reset { } #property-serverScripts-status { - position: relative; - top: -3px; - right: -20px; + position: relative; + top: -3px; + right: -20px; } #properties-list #collision-info > fieldset:first-of-type { diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 03dad80be6..d8ffc2ee25 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1,5 +1,4 @@ // entityProperties.js -// entityProperties.js // // Created by Ryan Huffman on 13 Nov 2014 // Copyright 2014 High Fidelity, Inc. @@ -9,7 +8,7 @@ /* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, HifiEntityUI, JSONEditor, openEventBridge, setTimeout, window, _ $ */ - + const ICON_FOR_TYPE = { Box: "V", Sphere: "n", @@ -25,7 +24,7 @@ const ICON_FOR_TYPE = { Multiple: "", PolyLine: "", Material: "" -}; +}; const PI = 3.14159265358979; const DEGREES_TO_RADIANS = PI / 180.0; @@ -37,420 +36,406 @@ const GROUPS = [ { id: "base", properties: [ - { + { label: NO_SELECTION, type: "icon", - icons: ICON_FOR_TYPE, + icons: ICON_FOR_TYPE, propertyName: "type", }, - { + { label: "Name", type: "string", propertyName: "name", }, - { + { label: "ID", type: "string", propertyName: "id", readOnly: true, }, - { + { label: "Parent", type: "string", propertyName: "parentID", }, - { + { label: "Locked", - glyph: "", + glyph: "", type: "bool", propertyName: "locked", }, - { + { label: "Visible", - glyph: "", + glyph: "", type: "bool", propertyName: "visible", }, ] }, - { + { id: "shape", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Shape", type: "dropdown", - options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, + options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, propertyName: "shape", }, - { + { label: "Color", type: "color", propertyName: "color", }, - /* - { - label: "Material", - type: "string", - propertyName: "", - }, - */ ] }, - { + { id: "text", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Text", type: "string", propertyName: "text", }, - { + { label: "Text Color", type: "color", propertyName: "textColor", }, - { + { label: "Background Color", type: "color", propertyName: "backgroundColor", }, - /* - { - label: "Transparent Background", - type: "bool", - propertyName: "" - }, - */ - { - label: "Line Height", + { + label: "Line Height", type: "number", - min: 0, - step: 0.005, - fixedDecimals: 4, - unit: "m", + min: 0, + step: 0.005, + fixedDecimals: 4, + unit: "m", propertyName: "lineHeight" }, - { + { label: "Face Camera", type: "bool", propertyName: "faceCamera" }, ] }, - { + { id: "zone", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Flying Allowed", type: "bool", propertyName: "flyingAllowed", }, - { + { label: "Ghosting Allowed", type: "bool", propertyName: "ghostingAllowed", }, - { + { label: "Filter", type: "string", propertyName: "filterURL", }, - { + { label: "Key Light", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "keyLightMode", - + }, - { + { label: "Key Light Color", type: "color", propertyName: "keyLight.color", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Light Intensity", type: "number", - min: 0, - max: 10, - step: 0.1, - fixedDecimals: 2, + min: 0, + max: 10, + step: 0.1, + fixedDecimals: 2, propertyName: "keyLight.intensity", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Light Altitude", type: "number", - fixedDecimals: 2, - unit: "deg", + fixedDecimals: 2, + unit: "deg", propertyName: "keyLight.direction.y", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Light Azimuth", type: "number", - fixedDecimals: 2, - unit: "deg", + fixedDecimals: 2, + unit: "deg", propertyName: "keyLight.direction.x", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Cast Shadows", type: "bool", propertyName: "keyLight.castShadows", - showPropertyRule: { "keyLightMode": "enabled" }, + showPropertyRule: { "keyLightMode": "enabled" }, }, - { + { label: "Skybox", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "skyboxMode", }, - { + { label: "Skybox Color", type: "color", propertyName: "skybox.color", - showPropertyRule: { "skyboxMode": "enabled" }, + showPropertyRule: { "skyboxMode": "enabled" }, }, - { + { label: "Skybox URL", type: "string", propertyName: "skybox.url", - showPropertyRule: { "skyboxMode": "enabled" }, + showPropertyRule: { "skyboxMode": "enabled" }, }, - { + { type: "buttons", - buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], + buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], propertyName: "copyURLToAmbient", - showPropertyRule: { "skyboxMode": "enabled" }, + showPropertyRule: { "skyboxMode": "enabled" }, }, - { + { label: "Ambient Light", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "ambientLightMode", }, - { + { label: "Ambient Intensity", type: "number", - min: 0, - max: 10, - step: 0.1, - fixedDecimals: 2, + min: 0, + max: 10, + step: 0.1, + fixedDecimals: 2, propertyName: "ambientLight.ambientIntensity", - showPropertyRule: { "ambientLightMode": "enabled" }, + showPropertyRule: { "ambientLightMode": "enabled" }, }, - { + { label: "Ambient URL", type: "string", propertyName: "ambientLight.ambientURL", - showPropertyRule: { "ambientLightMode": "enabled" }, + showPropertyRule: { "ambientLightMode": "enabled" }, }, - { + { label: "Haze", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "hazeMode", }, - { + { label: "Range", type: "number", - min: 5, - max: 10000, - step: 5, - fixedDecimals: 0, - unit: "m", + min: 5, + max: 10000, + step: 5, + fixedDecimals: 0, + unit: "m", propertyName: "haze.hazeRange", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Use Altitude", type: "bool", propertyName: "haze.hazeAltitudeEffect", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Base", type: "number", - min: -1000, - max: 1000, - step: 10, - fixedDecimals: 0, - unit: "m", + min: -1000, + max: 1000, + step: 10, + fixedDecimals: 0, + unit: "m", propertyName: "haze.hazeBaseRef", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Ceiling", type: "number", - min: -1000, - max: 5000, - step: 10, - fixedDecimals: 0, - unit: "m", + min: -1000, + max: 5000, + step: 10, + fixedDecimals: 0, + unit: "m", propertyName: "haze.hazeCeiling", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Haze Color", type: "color", propertyName: "haze.hazeColor", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Background Blend", type: "number", - min: 0.0, - max: 1.0, - step: 0.01, - fixedDecimals: 2, + min: 0.0, + max: 1.0, + step: 0.01, + fixedDecimals: 2, propertyName: "haze.hazeBackgroundBlend", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Enable Glare", type: "bool", propertyName: "haze.hazeEnableGlare", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Glare Color", type: "color", propertyName: "haze.hazeGlareColor", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Glare Angle", type: "number", - min: 0, - max: 180, - step: 1, - fixedDecimals: 0, + min: 0, + max: 180, + step: 1, + fixedDecimals: 0, propertyName: "haze.hazeGlareAngle", - showPropertyRule: { "hazeMode": "enabled" }, + showPropertyRule: { "hazeMode": "enabled" }, }, - { + { label: "Bloom", type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, propertyName: "bloomMode", }, - { + { label: "Bloom Intensity", type: "number", - min: 0, - max: 1, - step: 0.01, - fixedDecimals: 2, + min: 0, + max: 1, + step: 0.01, + fixedDecimals: 2, propertyName: "bloom.bloomIntensity", - showPropertyRule: { "bloomMode": "enabled" }, + showPropertyRule: { "bloomMode": "enabled" }, }, - { + { label: "Bloom Threshold", type: "number", - min: 0, - min: 1, - step: 0.01, - fixedDecimals: 2, + min: 0, + min: 1, + step: 0.01, + fixedDecimals: 2, propertyName: "bloom.bloomThreshold", - showPropertyRule: { "bloomMode": "enabled" }, + showPropertyRule: { "bloomMode": "enabled" }, }, - { + { label: "Bloom Size", type: "number", - min: 0, - min: 2, - step: 0.01, - fixedDecimals: 2, + min: 0, + min: 2, + step: 0.01, + fixedDecimals: 2, propertyName: "bloom.bloomSize", - showPropertyRule: { "bloomMode": "enabled" }, + showPropertyRule: { "bloomMode": "enabled" }, }, ] }, - { + { id: "model", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Model", type: "string", propertyName: "modelURL", }, - { + { label: "Collision Shape", type: "dropdown", - options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, + options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, propertyName: "shapeType", }, - { + { label: "Compound Shape", type: "string", propertyName: "compoundShapeURL", }, - { + { label: "Animation", type: "string", propertyName: "animation.url", }, - { + { label: "Play Automatically", type: "bool", propertyName: "animation.running", }, - { + { label: "Allow Transition", type: "bool", propertyName: "animation.allowTranslation", }, - { + { label: "Loop", type: "bool", propertyName: "animation.loop", }, - { + { label: "Hold", type: "bool", propertyName: "animation.hold", }, - { + { label: "Animation Frame", type: "number", propertyName: "animation.currentFrame", }, - { + { label: "First Frame", type: "number", propertyName: "animation.firstFrame", }, - { + { label: "Last Frame", type: "number", propertyName: "animation.lastFrame", }, - { + { label: "Animation FPS", type: "number", propertyName: "animation.fps", }, - { + { label: "Texture", type: "textarea", propertyName: "textures", }, - { + { label: "Original Texture", type: "textarea", propertyName: "originalTextures", - readOnly: true, + readOnly: true, }, ] }, - { + { id: "image", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Image", @@ -459,135 +444,135 @@ const GROUPS = [ }, ] }, - { + { id: "web", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Source", type: "string", propertyName: "sourceUrl", }, - { + { label: "Source Resolution", type: "number", propertyName: "dpi", }, ] }, - { + { id: "light", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Light Color", type: "color", propertyName: "lightColor", // this actually shares "color" property with shape Color - // but separating naming here to separate property fields + // but separating naming here to separate property fields }, - { + { label: "Intensity", type: "number", - min: 0, - step: 0.1, - fixedDecimals: 1, + min: 0, + step: 0.1, + fixedDecimals: 1, propertyName: "intensity", }, - { + { label: "Fall-Off Radius", type: "number", - min: 0, - step: 0.1, - fixedDecimals: 1, - unit: "m", + min: 0, + step: 0.1, + fixedDecimals: 1, + unit: "m", propertyName: "falloffRadius", }, - { + { label: "Spotlight", type: "bool", propertyName: "isSpotlight", }, - { + { label: "Spotlight Exponent", type: "number", - step: 0.01, - fixedDecimals: 2, + step: 0.01, + fixedDecimals: 2, propertyName: "exponent", }, - { + { label: "Spotlight Cut-Off", type: "number", - step: 0.01, - fixedDecimals: 2, + step: 0.01, + fixedDecimals: 2, propertyName: "cutoff", }, ] }, - { + { id: "material", - addToGroup: "base", + addToGroup: "base", properties: [ { label: "Material URL", type: "string", propertyName: "materialURL", }, - { + { label: "Material Data", type: "textarea", - buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, - { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], + buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, + { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], propertyName: "materialData", }, - { + { label: "Submesh to Replace", type: "number", - min: 0, - step: 1, + min: 0, + step: 1, propertyName: "submeshToReplace", }, - { + { label: "Material Name to Replace", type: "string", propertyName: "materialNameToReplace", }, - { + { label: "Select Submesh", type: "bool", propertyName: "selectSubmesh", }, - { + { label: "Priority", type: "number", - min: 0, + min: 0, propertyName: "priority", }, - { + { label: "Material Position", type: "vec2", - min: 0, - min: 1, - step: 0.1, - vec2Type: "xy", - subLabels: [ "x", "y" ], + min: 0, + min: 1, + step: 0.1, + vec2Type: "xy", + subLabels: [ "x", "y" ], propertyName: "materialMappingPos", }, - { + { label: "Material Scale", type: "vec2", - min: 0, - step: 0.1, - vec2Type: "wh", - subLabels: [ "width", "height" ], + min: 0, + step: 0.1, + vec2Type: "wh", + subLabels: [ "width", "height" ], propertyName: "materialMappingScale", }, - { + { label: "Material Rotation", type: "number", - step: 0.1, - fixedDecimals: 2, - unit: "deg", + step: 0.1, + fixedDecimals: 2, + unit: "deg", propertyName: "materialMappingRot", }, ] @@ -599,292 +584,292 @@ const GROUPS = [ { label: "Position", type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m", propertyName: "position", }, - { + { label: "Rotation", type: "vec3", - step: 0.1, - vec3Type: "pyr", - subLabels: [ "pitch", "yaw", "roll" ], - unit: "deg", + step: 0.1, + vec3Type: "pyr", + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg", propertyName: "rotation", }, - { + { label: "Dimension", type: "vec3", - step: 0.1, - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m", + step: 0.1, + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m", propertyName: "dimensions", }, - { + { label: "Scale", type: "number", - defaultValue: 100, - unit: "%", - buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, - { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], + defaultValue: 100, + unit: "%", + buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, + { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], propertyName: "scale", }, - { + { label: "Pivot", type: "vec3", - step: 0.1, - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "(ratio of dimension)", + step: 0.1, + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "(ratio of dimension)", propertyName: "registrationPoint", }, - { + { label: "Align", type: "buttons", - buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, - { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], + buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, + { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], propertyName: "alignToGrid", }, ] }, - { + { id: "collision", label: "COLLISION", - twoColumn: true, + twoColumn: true, properties: [ { label: "Collides", type: "bool", propertyName: "collisionless", - inverse: true, - column: -1, // before two columns div + inverse: true, + column: -1, // before two columns div }, - { + { label: "Dynamic", type: "bool", propertyName: "dynamic", - column: -1, // before two columns div + column: -1, // before two columns div }, - { + { label: "Collides With", type: "sub-header", - propertyName: "collidesWithHeader", - showPropertyRule: { "collisionless": "false" }, - column: 1, + propertyName: "collidesWithHeader", + showPropertyRule: { "collisionless": "false" }, + column: 1, }, - { + { label: "", type: "sub-header", - propertyName: "collidesWithHeaderHelper", - showPropertyRule: { "collisionless": "false" }, - column: 2, + propertyName: "collidesWithHeaderHelper", + showPropertyRule: { "collisionless": "false" }, + column: 2, }, - { + { label: "Static Entities", type: "bool", propertyName: "static", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 1, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, }, - { + { label: "Dynamic Entities", type: "bool", propertyName: "dynamic", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 2, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 2, }, - { + { label: "Kinematic Entities", type: "bool", propertyName: "kinematic", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 1, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, }, - { + { label: "My Avatar", type: "bool", propertyName: "myAvatar", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 2, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 2, }, - { + { label: "Other Avatars", type: "bool", propertyName: "otherAvatar", - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 1, + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, + column: 1, }, - { + { label: "Collision sound URL", type: "string", propertyName: "collisionSoundURL", - showPropertyRule: { "collisionless": "false" }, + showPropertyRule: { "collisionless": "false" }, }, ] }, - { + { id: "behavior", label: "BEHAVIOR", - twoColumn: true, + twoColumn: true, properties: [ { label: "Grabbable", type: "bool", propertyName: "grabbable", - column: 1, + column: 1, }, - { + { label: "Triggerable", type: "bool", propertyName: "triggerable", - column: 2, + column: 2, }, - { + { label: "Cloneable", type: "bool", propertyName: "cloneable", - column: 1, + column: 1, }, - { + { label: "Ignore inverse kinematics", type: "bool", propertyName: "ignoreIK", - column: 2, + column: 2, }, - { + { label: "Clone Lifetime", type: "number", - unit: "s", + unit: "s", propertyName: "cloneLifetime", - showPropertyRule: { "cloneable": "true" }, - column: 1, + showPropertyRule: { "cloneable": "true" }, + column: 1, }, - { + { label: "Clone Limit", type: "number", propertyName: "cloneLimit", - showPropertyRule: { "cloneable": "true" }, - column: 1, + showPropertyRule: { "cloneable": "true" }, + column: 1, }, - { + { label: "Clone Dynamic", type: "bool", propertyName: "cloneDynamic", - showPropertyRule: { "cloneable": "true" }, - column: 1, + showPropertyRule: { "cloneable": "true" }, + column: 1, }, - { + { label: "Clone Avatar Entity", type: "bool", propertyName: "cloneAvatarEntity", - showPropertyRule: { "cloneable": "true" }, - column: 1, + showPropertyRule: { "cloneable": "true" }, + column: 1, }, - { + { label: "Can cast shadow", type: "bool", propertyName: "castShadow", }, - { + { label: "Script", type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], propertyName: "script", }, - { + { label: "Server Script", type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], propertyName: "serverScripts", }, - { + { label: "Lifetime", type: "number", - unit: "s", + unit: "s", propertyName: "lifetime", }, - { + { label: "User Data", type: "textarea", - buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, - { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], propertyName: "userData", }, - ] + ] }, - { + { id: "physics", label: "PHYSICS", properties: [ { label: "Linear Velocity", type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m/s", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s", propertyName: "velocity", }, - { + { label: "Linear Damping", type: "number", - fixedDecimals: 2, + fixedDecimals: 2, propertyName: "damping", }, - { + { label: "Angular Velocity", type: "vec3", - multiplier: DEGREES_TO_RADIANS, - vec3Type: "pyr", - subLabels: [ "pitch", "yaw", "roll" ], - unit: "deg/s", + multiplier: DEGREES_TO_RADIANS, + vec3Type: "pyr", + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg/s", propertyName: "angularVelocity", }, - { + { label: "Angular Damping", type: "number", - fixedDecimals: 4, + fixedDecimals: 4, propertyName: "angularDamping", }, - { + { label: "Bounciness", type: "number", - fixedDecimals: 4, + fixedDecimals: 4, propertyName: "restitution", }, - { + { label: "Friction", type: "number", - fixedDecimals: 4, + fixedDecimals: 4, propertyName: "friction", }, - { + { label: "Density", type: "number", - fixedDecimals: 4, + fixedDecimals: 4, propertyName: "density", }, - { + { label: "Gravity", type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m/s2", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s2", propertyName: "gravity", }, - { + { label: "Acceleration", type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - unit: "m/s2", + vec3Type: "xyz", + subLabels: [ "x", "y", "z" ], + unit: "m/s2", propertyName: "acceleration", }, ] @@ -980,21 +965,21 @@ function showElements(els, show) { function updateProperty(propertyName, propertyValue) { var properties = {}; - let splitPropertyName = propertyName.split('.'); - if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; - properties[propertyGroupName] = {}; - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - properties[propertyGroupName][subPropertyName] = {}; - properties[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; - } else { - properties[propertyGroupName][subPropertyName] = propertyValue; - } - } else { - properties[propertyName] = propertyValue; - } + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + properties[propertyGroupName] = {}; + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + properties[propertyGroupName][subPropertyName] = {}; + properties[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; + } else { + properties[propertyGroupName][subPropertyName] = propertyValue; + } + } else { + properties[propertyName] = propertyValue; + } updateProperties(properties); } @@ -1117,27 +1102,27 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen } function clearUserData() { - let elUserData = elPropertyElements["userData"]; - deleteJSONEditor(); - elUserData.value = ""; - showUserDataTextArea(); - showNewJSONEditorButton(); - hideSaveUserDataButton(); - updateProperty('userData', elUserData.value); + let elUserData = elPropertyElements["userData"]; + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showNewJSONEditorButton(); + hideSaveUserDataButton(); + updateProperty('userData', elUserData.value); } function newJSONEditor() { - deleteJSONEditor(); - createJSONEditor(); - var data = {}; - setEditorJSON(data); - hideUserDataTextArea(); - hideNewJSONEditorButton(); - showSaveUserDataButton(); + deleteJSONEditor(); + createJSONEditor(); + var data = {}; + setEditorJSON(data); + hideUserDataTextArea(); + hideNewJSONEditorButton(); + showSaveUserDataButton(); } function saveUserData() { - saveJSONUserData(true); + saveJSONUserData(true); } function setUserDataFromEditor(noUpdate) { @@ -1287,7 +1272,7 @@ function hideUserDataTextArea() { } function hideUserDataSaved() { - $('#property-userData-saved').hide(); + $('#property-userData-saved').hide(); } function showStaticUserData() { @@ -1335,27 +1320,27 @@ function saveJSONUserData(noUpdate) { } function clearMaterialData() { - let elMaterialData = elPropertyElements["materialData"]; - deleteJSONMaterialEditor(); - elMaterialData.value = ""; - showMaterialDataTextArea(); - showNewJSONMaterialEditorButton(); - hideSaveMaterialDataButton(); - updateProperty('materialData', elMaterialData.value); + let elMaterialData = elPropertyElements["materialData"]; + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showNewJSONMaterialEditorButton(); + hideSaveMaterialDataButton(); + updateProperty('materialData', elMaterialData.value); } function newJSONMaterialEditor() { - deleteJSONMaterialEditor(); - createJSONMaterialEditor(); - var data = {}; - setMaterialEditorJSON(data); - hideMaterialDataTextArea(); - hideNewJSONMaterialEditorButton(); - showSaveMaterialDataButton(); + deleteJSONMaterialEditor(); + createJSONMaterialEditor(); + var data = {}; + setMaterialEditorJSON(data); + hideMaterialDataTextArea(); + hideNewJSONMaterialEditorButton(); + showSaveMaterialDataButton(); } function saveMaterialData() { - saveJSONMaterialData(true); + saveJSONMaterialData(true); } function setMaterialDataFromEditor(noUpdate) { @@ -1424,7 +1409,7 @@ function hideSaveMaterialDataButton() { } function disableSaveMaterialDataButton() { - $('#property-materialData-button-save').attr('disabled', true); + $('#property-materialData-button-save').attr('disabled', true); } function showNewJSONMaterialEditorButton() { @@ -1444,7 +1429,7 @@ function hideMaterialDataTextArea() { } function hideMaterialDataSaved() { - $('#property-materialData-saved').hide(); + $('#property-materialData-saved').hide(); } function showStaticMaterialData() { @@ -1525,8 +1510,8 @@ function unbindAllInputs() { } function setTextareaScrolling(element) { - var isScrolling = element.scrollHeight > element.offsetHeight; - element.setAttribute("scrolling", isScrolling ? "true" : "false"); + var isScrolling = element.scrollHeight > element.offsetHeight; + element.setAttribute("scrolling", isScrolling ? "true" : "false"); } function showParentMaterialNameBox(number, elNumber, elString) { @@ -1542,557 +1527,557 @@ function showParentMaterialNameBox(number, elNumber, elString) { } function rescaleDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "rescaleDimensions", - percentage: parseFloat(document.getElementById("property-scale").value) - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseFloat(document.getElementById("property-scale").value) + })); } function moveSelectionToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveSelectionToGrid" - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveSelectionToGrid" + })); } function moveAllToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveAllToGrid" - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveAllToGrid" + })); } function resetToNaturalDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "resetToNaturalDimensions" - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "resetToNaturalDimensions" + })); } function reloadScripts() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadClientScripts" - })); + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadClientScripts" + })); } function reloadServerScripts() { - // invalidate the current status (so that same-same updates can still be observed visually) - document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadServerScripts" - })); + // invalidate the current status (so that same-same updates can still be observed visually) + document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadServerScripts" + })); } function copySkyboxURLToAmbientURL() { - let skyboxURL = elPropertyElements["skybox.url"].value; - elPropertyElements["ambientLight.ambientURL"].value = skyboxURL; - updateProperty("ambientLight.ambientURL", skyboxURL); + let skyboxURL = elPropertyElements["skybox.url"].value; + elPropertyElements["ambientLight.ambientURL"].value = skyboxURL; + updateProperty("ambientLight.ambientURL", skyboxURL); } function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { - let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); - let elDiv = document.createElement('div'); - let elLabel = document.createElement('label'); - elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; - elLabel.setAttribute("for", elementPropertyID); - let elInput = document.createElement('input'); - elInput.setAttribute("id", elementPropertyID); - elInput.setAttribute("type", "number"); - elInput.setAttribute("class", subLabel); - if (min !== undefined) { - elInput.setAttribute("min", min); - } - if (max !== undefined) { - elInput.setAttribute("max", max); - } - if (step !== undefined) { - elInput.setAttribute("step", step); - } - elDiv.appendChild(elInput); - elDiv.appendChild(elLabel); - elTuple.appendChild(elDiv); - return elInput; + let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; + elLabel.setAttribute("for", elementPropertyID); + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementPropertyID); + elInput.setAttribute("type", "number"); + elInput.setAttribute("class", subLabel); + if (min !== undefined) { + elInput.setAttribute("min", min); + } + if (max !== undefined) { + elInput.setAttribute("max", max); + } + if (step !== undefined) { + elInput.setAttribute("step", step); + } + elDiv.appendChild(elInput); + elDiv.appendChild(elLabel); + elTuple.appendChild(elDiv); + return elInput; } function addUnit(unit, elLabel) { - if (unit !== undefined) { - let elSpan = document.createElement('span'); - elSpan.setAttribute("class", "unit"); - elSpan.innerHTML = unit; - elLabel.appendChild(elSpan); - } + if (unit !== undefined) { + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "unit"); + elSpan.innerHTML = unit; + elLabel.appendChild(elSpan); + } } function addButtons(elProperty, propertyID, buttons, newRow) { - let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "row"); - - buttons.forEach(function(button) { - let elButton = document.createElement('input'); - elButton.setAttribute("type", "button"); - elButton.setAttribute("class", button.className); - elButton.setAttribute("id", propertyID + "-button-" + button.id); - elButton.setAttribute("value", button.label); - elButton.addEventListener("click", button.onClick); - if (newRow) { - elDiv.appendChild(elButton); - } else { - elProperty.appendChild(elButton); - } - }); - - if (newRow) { - elProperty.appendChild(document.createElement('br')); - elProperty.appendChild(elDiv); - } + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "row"); + + buttons.forEach(function(button) { + let elButton = document.createElement('input'); + elButton.setAttribute("type", "button"); + elButton.setAttribute("class", button.className); + elButton.setAttribute("id", propertyID + "-button-" + button.id); + elButton.setAttribute("value", button.label); + elButton.addEventListener("click", button.onClick); + if (newRow) { + elDiv.appendChild(elButton); + } else { + elProperty.appendChild(elButton); + } + }); + + if (newRow) { + elProperty.appendChild(document.createElement('br')); + elProperty.appendChild(elDiv); + } } function showGroupsForType(type) { - if (type === "Box" || type === "Sphere") { - type = "Shape"; - } - - let typeGroups = GROUPS_PER_TYPE[type]; + if (type === "Box" || type === "Sphere") { + type = "Shape"; + } + + let typeGroups = GROUPS_PER_TYPE[type]; - for (let groupKey in elGroups) { - let elGroup = elGroups[groupKey]; - if (typeGroups && typeGroups.indexOf(groupKey) > -1) { - elGroup.style.display = "block"; - } else { - elGroup.style.display = "none"; - } - } + for (let groupKey in elGroups) { + let elGroup = elGroups[groupKey]; + if (typeGroups && typeGroups.indexOf(groupKey) > -1) { + elGroup.style.display = "block"; + } else { + elGroup.style.display = "none"; + } + } } function resetProperties() { - for (let propertyName in elPropertyElements) { - let elProperty = elPropertyElements[propertyName]; - if (elProperty instanceof Array) { - if (elProperty.length === 2 || elProperty.length === 3) { - // vec2/vec3 are array of 2/3 input numbers - elProperty[0].value = ""; - elProperty[1].value = ""; - if (elProperty[2] !== undefined) { - elProperty[2].value = ""; - } - } else if (elProperty.length === 4) { - // color is array of color picker and 3 input numbers - elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elProperty[1].value = ""; - elProperty[2].value = ""; - elProperty[3].value = ""; - } - } else if (elProperty.getAttribute("type") === "number") { - if (elProperty.getAttribute("defaultValue")) { - elProperty.value = elProperty.getAttribute("defaultValue"); - } else { - elProperty.value = ""; - } - } else if (elProperty.getAttribute("type") === "checkbox") { - elProperty.checked = false; - } else { - elProperty.value = ""; - } - } - - for (let showPropertyRule in showPropertyRules) { - let propertyShowRules = showPropertyRules[showPropertyRule]; - for (let propertyToHide in propertyShowRules) { - let elPropertyToHide = elPropertyElements[propertyToHide]; - if (elPropertyToHide) { - let parentNode = elPropertyToHide.parentNode; - if (parentNode === undefined && elPropertyToHide instanceof Array) { - parentNode = elPropertyToHide[0].parentNode; - } - parentNode.style.display = "none"; - } - } - } + for (let propertyName in elPropertyElements) { + let elProperty = elPropertyElements[propertyName]; + if (elProperty instanceof Array) { + if (elProperty.length === 2 || elProperty.length === 3) { + // vec2/vec3 are array of 2/3 input numbers + elProperty[0].value = ""; + elProperty[1].value = ""; + if (elProperty[2] !== undefined) { + elProperty[2].value = ""; + } + } else if (elProperty.length === 4) { + // color is array of color picker and 3 input numbers + elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elProperty[1].value = ""; + elProperty[2].value = ""; + elProperty[3].value = ""; + } + } else if (elProperty.getAttribute("type") === "number") { + if (elProperty.getAttribute("defaultValue")) { + elProperty.value = elProperty.getAttribute("defaultValue"); + } else { + elProperty.value = ""; + } + } else if (elProperty.getAttribute("type") === "checkbox") { + elProperty.checked = false; + } else { + elProperty.value = ""; + } + } + + for (let showPropertyRule in showPropertyRules) { + let propertyShowRules = showPropertyRules[showPropertyRule]; + for (let propertyToHide in propertyShowRules) { + let elPropertyToHide = elPropertyElements[propertyToHide]; + if (elPropertyToHide) { + let parentNode = elPropertyToHide.parentNode; + if (parentNode === undefined && elPropertyToHide instanceof Array) { + parentNode = elPropertyToHide[0].parentNode; + } + parentNode.style.display = "none"; + } + } + } } function loaded() { - openEventBridge(function() { - let elPropertiesList = document.getElementById("properties-list"); - - GROUPS.forEach(function(group) { - let elGroup; - if (group.addToGroup !== undefined) { - let fieldset = document.getElementById(group.addToGroup); - elGroup = document.createElement('div'); - fieldset.appendChild(elGroup); - } else { - elGroup = document.createElement('fieldset'); - elGroup.setAttribute("class", "major"); - elGroup.setAttribute("id", group.id); - elPropertiesList.appendChild(elGroup); - } + openEventBridge(function() { + let elPropertiesList = document.getElementById("properties-list"); + + GROUPS.forEach(function(group) { + let elGroup; + if (group.addToGroup !== undefined) { + let fieldset = document.getElementById(group.addToGroup); + elGroup = document.createElement('div'); + fieldset.appendChild(elGroup); + } else { + elGroup = document.createElement('fieldset'); + elGroup.setAttribute("class", "major"); + elGroup.setAttribute("id", group.id); + elPropertiesList.appendChild(elGroup); + } - if (group.label !== undefined) { - let elLegend = document.createElement('legend'); - elLegend.innerText = group.label; - elLegend.setAttribute("class", "section-header"); - let elSpan = document.createElement('span'); - elSpan.setAttribute("class", ".collapse-icon"); - elSpan.innerText = "M"; - elLegend.appendChild(elSpan); - elGroup.appendChild(elLegend); - } - - group.properties.forEach(function(property) { - let propertyType = property.type; - let propertyName = property.propertyName; - let propertyID = "property-" + propertyName; - propertyID = propertyID.replace(".", "-"); - - let elProperty; - if (propertyType === "sub-header") { - elProperty = document.createElement('legend'); - elProperty.innerText = property.label; - elProperty.setAttribute("class", "sub-section-header"); - } else { - elProperty = document.createElement('div'); - elProperty.setAttribute("id", "div-" + propertyName); - } - - if (group.twoColumn && property.column !== undefined && property.column !== -1) { - let columnName = group.id + "column" + property.column; - let elColumn = document.getElementById(columnName); - if (!elColumn) { - let columnDivName = group.id + "columnDiv"; - let elColumnDiv = document.getElementById(columnDivName); - if (!elColumnDiv) { - elColumnDiv = document.createElement('div'); - elColumnDiv.setAttribute("class", "two-column"); - elColumnDiv.setAttribute("id", group.id + "columnDiv"); - elGroup.appendChild(elColumnDiv); - } - elColumn = document.createElement('fieldset'); - elColumn.setAttribute("class", "column"); - elColumn.setAttribute("id", columnName); - elColumnDiv.appendChild(elColumn); - } - elColumn.appendChild(elProperty); - } else { - elGroup.appendChild(elProperty); - } - - let elLabel = document.createElement('label'); - elLabel.innerText = property.label; - elLabel.setAttribute("for", propertyID); - - switch (propertyType) { - case 'vec3': { - elProperty.setAttribute("class", "property " + property.vec3Type + " fstuple"); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - addUnit(property.unit, elLabel); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); - let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); - let elInputZ = createTupleNumberInput(elTuple, propertyID, property.subLabels[2], property.min, property.max, property.step); - - let inputChangeFunction; - let multiplier = 1; - if (property.multiplier !== undefined) { - multiplier = property.multiplier; - inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); - } else { - inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, elInputZ, property.multiplier); - } - elInputX.setAttribute("multiplier", multiplier); - elInputY.setAttribute("multiplier", multiplier); - elInputZ.setAttribute("multiplier", multiplier); - elInputX.addEventListener('change', inputChangeFunction); - elInputY.addEventListener('change', inputChangeFunction); - elInputZ.addEventListener('change', inputChangeFunction); - - elPropertyElements[propertyName] = [elInputX, elInputY, elInputZ]; - break; - } - case 'vec2': { - elProperty.setAttribute("class", "property " + property.vec2Type + " fstuple"); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - addUnit(property.unit, elLabel); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); - let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); - - let inputChangeFunction; - let multiplier = 1; - if (property.multiplier !== undefined) { - multiplier = property.multiplier; - inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY); - } else { - inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, property.multiplier); - } - elInputX.setAttribute("multiplier", multiplier); - elInputY.setAttribute("multiplier", multiplier); - elInputX.addEventListener('change', inputChangeFunction); - elInputY.addEventListener('change', inputChangeFunction); - - elPropertyElements[propertyName] = [elInputX, elInputY]; - break; - } - case 'color': { - elProperty.setAttribute("class", "property rgb fstuple"); - - let elColorPicker = document.createElement('div'); - elColorPicker.setAttribute("class", "color-picker"); - elColorPicker.setAttribute("id", propertyID); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - elProperty.appendChild(elColorPicker); - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputR = createTupleNumberInput(elTuple, propertyID, "red", 0, 255, 1); - let elInputG = createTupleNumberInput(elTuple, propertyID, "green", 0, 255, 1); - let elInputB = createTupleNumberInput(elTuple, propertyID, "blue", 0, 255, 1); - - let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); - elInputR.addEventListener('change', inputChangeFunction); - elInputG.addEventListener('change', inputChangeFunction); - elInputB.addEventListener('change', inputChangeFunction); - - let colorPickerID = "#" + propertyID; - colorPickers[colorPickerID] = $(colorPickerID).colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $(colorPickerID).attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers[colorPickerID].colpickSetColor({ - "r": elInputR.value, - "g": elInputG.value, - "b": elInputB.value - }); - }, - onHide: function(colpick) { - $(colorPickerID).attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elPropertyElements[propertyName] = [elColorPicker, elInputR, elInputG, elInputB]; - break; - } - case 'string': { - elProperty.setAttribute("class", "property text"); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "text"); - if (property.readOnly) { - elInput.readOnly = true; - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, false); - } - - elPropertyElements[propertyName] = elInput; - break; - } - case 'bool': { - elProperty.setAttribute("class", "property checkbox"); - - if (property.glyph !== undefined) { - elLabel.innerText = " " + property.label; - let elSpan = document.createElement('span'); - elSpan.innerHTML = property.glyph; - elLabel.insertBefore(elSpan, elLabel.firstChild); - } - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "checkbox"); - - let inverse = property.inverse; - elInput.setAttribute("inverse", inverse ? "true" : "false"); - - elProperty.appendChild(elInput); - elProperty.appendChild(elLabel); - - let subPropertyOf = property.subPropertyOf; - if (subPropertyOf !== undefined) { - elInput.setAttribute("subPropertyOf", subPropertyOf); - elPropertyElements[propertyName] = elInput; - subProperties.push(propertyName); - elInput.addEventListener('change', function() { - updateCheckedSubProperty(subPropertyOf, properties[subPropertyOf], elInput, propertyName); - }); - } else { - elPropertyElements[propertyName] = elInput; - elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, inverse)); - } - break; - } - case 'dropdown': { - elProperty.setAttribute("class", "property dropdown"); - - let elInput = document.createElement('select'); - elInput.setAttribute("id", propertyID); - - for (let optionKey in property.options) { - let option = document.createElement('option'); - option.value = optionKey; - option.text = property.options[optionKey]; - elInput.add(option); - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - elPropertyElements[propertyName] = elInput; - break; - } - case 'number': { - elProperty.setAttribute("class", "property number"); - - addUnit(property.unit, elLabel); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "number"); - if (property.min !== undefined) { - elInput.setAttribute("min", property.min); - } - if (property.max !== undefined) { - elInput.setAttribute("max", property.max); - } - if (property.step !== undefined) { - elInput.setAttribute("step", property.step); - } - - let fixedDecimals = property.fixedDecimals !== undefined ? property.fixedDecimals : 0; - elInput.setAttribute("fixedDecimals", fixedDecimals); - - let defaultValue = property.defaultValue; - if (defaultValue !== undefined) { - elInput.setAttribute("defaultValue", defaultValue); - elInput.value = defaultValue; - } - - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, true); - } - - elPropertyElements[propertyName] = elInput; - break; - } - case 'textarea': { - elProperty.setAttribute("class", "property textarea"); - - elProperty.appendChild(elLabel); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, true); - } - - let elInput = document.createElement('textarea'); - elInput.setAttribute("id", propertyID); - if (property.readOnly) { - elInput.readOnly = true; - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elInput); - elPropertyElements[propertyName] = elInput; - break; - } - case 'icon': { - elProperty.setAttribute("class", "property value"); - - elLabel.setAttribute("id", propertyID); - elLabel.innerHTML = " " + property.label; - - let elSpan = document.createElement('span'); - elSpan.setAttribute("id", propertyID + "-icon"); - icons[propertyName] = property.icons; + if (group.label !== undefined) { + let elLegend = document.createElement('legend'); + elLegend.innerText = group.label; + elLegend.setAttribute("class", "section-header"); + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", ".collapse-icon"); + elSpan.innerText = "M"; + elLegend.appendChild(elSpan); + elGroup.appendChild(elLegend); + } + + group.properties.forEach(function(property) { + let propertyType = property.type; + let propertyName = property.propertyName; + let propertyID = "property-" + propertyName; + propertyID = propertyID.replace(".", "-"); + + let elProperty; + if (propertyType === "sub-header") { + elProperty = document.createElement('legend'); + elProperty.innerText = property.label; + elProperty.setAttribute("class", "sub-section-header"); + } else { + elProperty = document.createElement('div'); + elProperty.setAttribute("id", "div-" + propertyName); + } + + if (group.twoColumn && property.column !== undefined && property.column !== -1) { + let columnName = group.id + "column" + property.column; + let elColumn = document.getElementById(columnName); + if (!elColumn) { + let columnDivName = group.id + "columnDiv"; + let elColumnDiv = document.getElementById(columnDivName); + if (!elColumnDiv) { + elColumnDiv = document.createElement('div'); + elColumnDiv.setAttribute("class", "two-column"); + elColumnDiv.setAttribute("id", group.id + "columnDiv"); + elGroup.appendChild(elColumnDiv); + } + elColumn = document.createElement('fieldset'); + elColumn.setAttribute("class", "column"); + elColumn.setAttribute("id", columnName); + elColumnDiv.appendChild(elColumn); + } + elColumn.appendChild(elProperty); + } else { + elGroup.appendChild(elProperty); + } + + let elLabel = document.createElement('label'); + elLabel.innerText = property.label; + elLabel.setAttribute("for", propertyID); + + switch (propertyType) { + case 'vec3': { + elProperty.setAttribute("class", "property " + property.vec3Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(property.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); + let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); + let elInputZ = createTupleNumberInput(elTuple, propertyID, property.subLabels[2], property.min, property.max, property.step); + + let inputChangeFunction; + let multiplier = 1; + if (property.multiplier !== undefined) { + multiplier = property.multiplier; + inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); + } else { + inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, elInputZ, property.multiplier); + } + elInputX.setAttribute("multiplier", multiplier); + elInputY.setAttribute("multiplier", multiplier); + elInputZ.setAttribute("multiplier", multiplier); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + elInputZ.addEventListener('change', inputChangeFunction); + + elPropertyElements[propertyName] = [elInputX, elInputY, elInputZ]; + break; + } + case 'vec2': { + elProperty.setAttribute("class", "property " + property.vec2Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(property.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); + let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); + + let inputChangeFunction; + let multiplier = 1; + if (property.multiplier !== undefined) { + multiplier = property.multiplier; + inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY); + } else { + inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, property.multiplier); + } + elInputX.setAttribute("multiplier", multiplier); + elInputY.setAttribute("multiplier", multiplier); + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + + elPropertyElements[propertyName] = [elInputX, elInputY]; + break; + } + case 'color': { + elProperty.setAttribute("class", "property rgb fstuple"); + + let elColorPicker = document.createElement('div'); + elColorPicker.setAttribute("class", "color-picker"); + elColorPicker.setAttribute("id", propertyID); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + elProperty.appendChild(elColorPicker); + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputR = createTupleNumberInput(elTuple, propertyID, "red", 0, 255, 1); + let elInputG = createTupleNumberInput(elTuple, propertyID, "green", 0, 255, 1); + let elInputB = createTupleNumberInput(elTuple, propertyID, "blue", 0, 255, 1); + + let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); + elInputR.addEventListener('change', inputChangeFunction); + elInputG.addEventListener('change', inputChangeFunction); + elInputB.addEventListener('change', inputChangeFunction); + + let colorPickerID = "#" + propertyID; + colorPickers[colorPickerID] = $(colorPickerID).colpick({ + colorScheme: 'dark', + layout: 'hex', + color: '000000', + submit: false, // We don't want to have a submission button + onShow: function(colpick) { + $(colorPickerID).attr('active', 'true'); + // The original color preview within the picker needs to be updated on show because + // prior to the picker being shown we don't have access to the selections' starting color. + colorPickers[colorPickerID].colpickSetColor({ + "r": elInputR.value, + "g": elInputG.value, + "b": elInputB.value + }); + }, + onHide: function(colpick) { + $(colorPickerID).attr('active', 'false'); + }, + onChange: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + } + }); + + elPropertyElements[propertyName] = [elColorPicker, elInputR, elInputG, elInputB]; + break; + } + case 'string': { + elProperty.setAttribute("class", "property text"); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "text"); + if (property.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, false); + } + + elPropertyElements[propertyName] = elInput; + break; + } + case 'bool': { + elProperty.setAttribute("class", "property checkbox"); + + if (property.glyph !== undefined) { + elLabel.innerText = " " + property.label; + let elSpan = document.createElement('span'); + elSpan.innerHTML = property.glyph; + elLabel.insertBefore(elSpan, elLabel.firstChild); + } + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "checkbox"); + + let inverse = property.inverse; + elInput.setAttribute("inverse", inverse ? "true" : "false"); + + elProperty.appendChild(elInput); + elProperty.appendChild(elLabel); + + let subPropertyOf = property.subPropertyOf; + if (subPropertyOf !== undefined) { + elInput.setAttribute("subPropertyOf", subPropertyOf); + elPropertyElements[propertyName] = elInput; + subProperties.push(propertyName); + elInput.addEventListener('change', function() { + updateCheckedSubProperty(subPropertyOf, properties[subPropertyOf], elInput, propertyName); + }); + } else { + elPropertyElements[propertyName] = elInput; + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, inverse)); + } + break; + } + case 'dropdown': { + elProperty.setAttribute("class", "property dropdown"); + + let elInput = document.createElement('select'); + elInput.setAttribute("id", propertyID); + + for (let optionKey in property.options) { + let option = document.createElement('option'); + option.value = optionKey; + option.text = property.options[optionKey]; + elInput.add(option); + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + elPropertyElements[propertyName] = elInput; + break; + } + case 'number': { + elProperty.setAttribute("class", "property number"); + + addUnit(property.unit, elLabel); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyID); + elInput.setAttribute("type", "number"); + if (property.min !== undefined) { + elInput.setAttribute("min", property.min); + } + if (property.max !== undefined) { + elInput.setAttribute("max", property.max); + } + if (property.step !== undefined) { + elInput.setAttribute("step", property.step); + } + + let fixedDecimals = property.fixedDecimals !== undefined ? property.fixedDecimals : 0; + elInput.setAttribute("fixedDecimals", fixedDecimals); + + let defaultValue = property.defaultValue; + if (defaultValue !== undefined) { + elInput.setAttribute("defaultValue", defaultValue); + elInput.value = defaultValue; + } + + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, true); + } + + elPropertyElements[propertyName] = elInput; + break; + } + case 'textarea': { + elProperty.setAttribute("class", "property textarea"); + + elProperty.appendChild(elLabel); + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, true); + } + + let elInput = document.createElement('textarea'); + elInput.setAttribute("id", propertyID); + if (property.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elInput); + elPropertyElements[propertyName] = elInput; + break; + } + case 'icon': { + elProperty.setAttribute("class", "property value"); + + elLabel.setAttribute("id", propertyID); + elLabel.innerHTML = " " + property.label; + + let elSpan = document.createElement('span'); + elSpan.setAttribute("id", propertyID + "-icon"); + icons[propertyName] = property.icons; - elProperty.appendChild(elSpan); - elProperty.appendChild(elLabel); - elPropertyElements[propertyName] = [ elSpan, elLabel ]; - break; - } - case 'buttons': { - elProperty.setAttribute("class", "property Text"); - - let hasLabel = property.label !== undefined; - if (hasLabel) { - elProperty.appendChild(elLabel); - } - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, hasLabel); - } - - elPropertyElements[propertyName] = elProperty; - break; - } - case 'sub-header': { - if (propertyName !== undefined) { - elPropertyElements[propertyName] = elProperty; - } - break; - } - } - - let showPropertyRule = property.showPropertyRule; - if (showPropertyRule !== undefined) { - let dependentProperty = Object.keys(showPropertyRule)[0]; - let dependentPropertyValue = showPropertyRule[dependentProperty]; - if (!showPropertyRules[dependentProperty]) { - showPropertyRules[dependentProperty] = []; - } - showPropertyRules[dependentProperty][propertyName] = dependentPropertyValue; - } - }); - - elGroups[group.id] = elGroup; - }); - - if (window.EventBridge !== undefined) { + elProperty.appendChild(elSpan); + elProperty.appendChild(elLabel); + elPropertyElements[propertyName] = [ elSpan, elLabel ]; + break; + } + case 'buttons': { + elProperty.setAttribute("class", "property Text"); + + let hasLabel = property.label !== undefined; + if (hasLabel) { + elProperty.appendChild(elLabel); + } + + if (property.buttons !== undefined) { + addButtons(elProperty, propertyID, property.buttons, hasLabel); + } + + elPropertyElements[propertyName] = elProperty; + break; + } + case 'sub-header': { + if (propertyName !== undefined) { + elPropertyElements[propertyName] = elProperty; + } + break; + } + } + + let showPropertyRule = property.showPropertyRule; + if (showPropertyRule !== undefined) { + let dependentProperty = Object.keys(showPropertyRule)[0]; + let dependentPropertyValue = showPropertyRule[dependentProperty]; + if (!showPropertyRules[dependentProperty]) { + showPropertyRules[dependentProperty] = []; + } + showPropertyRules[dependentProperty][propertyName] = dependentPropertyValue; + } + }); + + elGroups[group.id] = elGroup; + }); + + if (window.EventBridge !== undefined) { var properties; EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type === "server_script_status") { - let elServerScriptError = document.getElementById("property-serverScripts-error"); - let elServerScriptStatus = document.getElementById("property-serverScripts-status"); + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); elServerScriptError.value = data.errorInfo; // If we just set elServerScriptError's diplay to block or none, we still end up with // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. @@ -2125,22 +2110,22 @@ function loaded() { deleteJSONMaterialEditor(); } } - - elPropertyElements["type"][0].style.display = "none"; + + elPropertyElements["type"][0].style.display = "none"; elPropertyElements["type"][1].innerHTML = NO_SELECTION; - elPropertiesList.className = ''; - showGroupsForType("None"); - - resetProperties(); - - deleteJSONEditor(); + elPropertiesList.className = ''; + showGroupsForType("None"); + + resetProperties(); + + deleteJSONEditor(); elPropertyElements["userData"].value = ""; showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); - - deleteJSONMaterialEditor(); - elPropertyElements["materialData"].value = ""; + + deleteJSONMaterialEditor(); + elPropertyElements["materialData"].value = ""; showMaterialDataTextArea(); showSaveMaterialDataButton(); showNewJSONMaterialEditorButton(); @@ -2149,8 +2134,8 @@ function loaded() { } else if (data.selections.length > 1) { deleteJSONEditor(); deleteJSONMaterialEditor(); - - let selections = data.selections; + + let selections = data.selections; let ids = []; let types = {}; @@ -2170,19 +2155,19 @@ function loaded() { if (numTypes === 1) { type = selections[0].properties.type; } - + elPropertyElements["type"][0].innerHTML = ICON_FOR_TYPE[type]; elPropertyElements["type"][0].style.display = "inline-block"; - elPropertyElements["type"][1].innerHTML = type + " (" + data.selections.length + ")"; - elPropertiesList.className = ''; - showGroupsForType(type); - - resetProperties(); - disableProperties(); + elPropertyElements["type"][1].innerHTML = type + " (" + data.selections.length + ")"; + elPropertiesList.className = ''; + showGroupsForType(type); + + resetProperties(); + disableProperties(); } else { properties = data.selections[0].properties; - - if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { + + if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { if (editor !== null) { saveUserData(); } @@ -2206,148 +2191,148 @@ function loaded() { properties.type = "Image"; } } - + // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; - showGroupsForType(properties.type); - - for (let propertyName in elPropertyElements) { - let elProperty = elPropertyElements[propertyName]; - - let propertyValue; - let splitPropertyName = propertyName.split('.'); - if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; - if (properties[propertyGroupName] === undefined || properties[propertyGroupName][subPropertyName] === undefined) { - continue; - } - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - propertyValue = properties[propertyGroupName][subPropertyName][subSubPropertyName]; - } else { - propertyValue = properties[propertyGroupName][subPropertyName]; - } - } else { - propertyValue = properties[propertyName]; - } + showGroupsForType(properties.type); + + for (let propertyName in elPropertyElements) { + let elProperty = elPropertyElements[propertyName]; + + let propertyValue; + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + if (properties[propertyGroupName] === undefined || properties[propertyGroupName][subPropertyName] === undefined) { + continue; + } + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + propertyValue = properties[propertyGroupName][subPropertyName][subSubPropertyName]; + } else { + propertyValue = properties[propertyGroupName][subPropertyName]; + } + } else { + propertyValue = properties[propertyName]; + } - // workaround for shape Color & Light Color property fields sharing same property value "color" - if (propertyValue === undefined && propertyName === "lightColor") { - propertyValue = properties["color"] - } - - let isSubProperty = subProperties.indexOf(propertyName) > -1; - if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { - continue; - } - - if (elProperty instanceof Array) { - if (elProperty[1].getAttribute("type") === "number") { // vectors - if (elProperty.length === 2 || elProperty.length === 3) { - // vec2/vec3 are array of 2/3 input numbers - elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); - elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); - if (elProperty[2] !== undefined) { - elProperty[2].value = (propertyValue.z * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); - } - } else if (elProperty.length === 4) { - // color is array of color picker and 3 input numbers - elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; - elProperty[1].value = propertyValue.red; - elProperty[2].value = propertyValue.green; - elProperty[3].value = propertyValue.blue; - } - } else if (elProperty[0].nodeName === "SPAN") { // icons - // icon is array of elSpan (icon glyph) and elLabel - elProperty[0].innerHTML = icons[propertyName][propertyValue]; - elProperty[0].style.display = "inline-block"; - elProperty[1].innerHTML = propertyValue; //+ " (" + data.selections.length + ")"; - } - } else if (elProperty.getAttribute("subPropertyOf")) { - let propertyValue = properties[elProperty.getAttribute("subPropertyOf")]; - let subProperties = propertyValue.split(","); - elProperty.checked = subProperties.indexOf(propertyName) > -1; - } else if (elProperty.getAttribute("type") === "number") { - let fixedDecimals = elProperty.getAttribute("fixedDecimals"); - elProperty.value = propertyValue.toFixed(fixedDecimals); - } else if (elProperty.getAttribute("type") === "checkbox") { - let inverse = elProperty.getAttribute("inverse") === "true"; - elProperty.checked = inverse ? !propertyValue : propertyValue; - } else if (elProperty.nodeName === "TEXTAREA") { - elProperty.value = propertyValue; - setTextareaScrolling(elProperty); - } else if (elProperty.nodeName === "SELECT") { // dropdown - elProperty.value = propertyValue; - } else { - elProperty.value = propertyValue; - } - - let propertyShowRules = showPropertyRules[propertyName]; - if (propertyShowRules !== undefined) { - for (let propertyToShow in propertyShowRules) { - let showIfThisPropertyValue = propertyShowRules[propertyToShow]; - let show = String(propertyValue) === String(showIfThisPropertyValue); - let elPropertyToShow = elPropertyElements[propertyToShow]; - if (elPropertyToShow) { - let parentNode = elPropertyToShow.parentNode; - if (parentNode === undefined && elPropertyToShow instanceof Array) { - parentNode = elPropertyToShow[0].parentNode; - } - parentNode.style.display = show ? "block" : "none"; - } - } - } - } - - let elGrabbable = elPropertyElements["grabbable"]; - let elTriggerable = elPropertyElements["triggerable"]; - let elIgnoreIK = elPropertyElements["ignoreIK"]; - elGrabbable.checked = elPropertyElements["dynamic"].checked; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - let grabbablesSet = false; - let parsedUserData = {}; - try { - parsedUserData = JSON.parse(properties.userData); - if ("grabbableKey" in parsedUserData) { - grabbablesSet = true; - let grabbableData = parsedUserData.grabbableKey; - if ("grabbable" in grabbableData) { - elGrabbable.checked = grabbableData.grabbable; - } else { - elGrabbable.checked = true; - } - if ("triggerable" in grabbableData) { - elTriggerable.checked = grabbableData.triggerable; - } else if ("wantsTrigger" in grabbableData) { - elTriggerable.checked = grabbableData.wantsTrigger; - } else { - elTriggerable.checked = false; - } - if ("ignoreIK" in grabbableData) { - elIgnoreIK.checked = grabbableData.ignoreIK; - } else { - elIgnoreIK.checked = true; - } - } - } catch (e) { - // TODO: What should go here? - } - if (!grabbablesSet) { - elGrabbable.checked = true; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - } - - if (properties.type === "Image") { + // workaround for shape Color & Light Color property fields sharing same property value "color" + if (propertyValue === undefined && propertyName === "lightColor") { + propertyValue = properties["color"] + } + + let isSubProperty = subProperties.indexOf(propertyName) > -1; + if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { + continue; + } + + if (elProperty instanceof Array) { + if (elProperty[1].getAttribute("type") === "number") { // vectors + if (elProperty.length === 2 || elProperty.length === 3) { + // vec2/vec3 are array of 2/3 input numbers + elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); + elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + if (elProperty[2] !== undefined) { + elProperty[2].value = (propertyValue.z * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + } + } else if (elProperty.length === 4) { + // color is array of color picker and 3 input numbers + elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; + elProperty[1].value = propertyValue.red; + elProperty[2].value = propertyValue.green; + elProperty[3].value = propertyValue.blue; + } + } else if (elProperty[0].nodeName === "SPAN") { // icons + // icon is array of elSpan (icon glyph) and elLabel + elProperty[0].innerHTML = icons[propertyName][propertyValue]; + elProperty[0].style.display = "inline-block"; + elProperty[1].innerHTML = propertyValue; //+ " (" + data.selections.length + ")"; + } + } else if (elProperty.getAttribute("subPropertyOf")) { + let propertyValue = properties[elProperty.getAttribute("subPropertyOf")]; + let subProperties = propertyValue.split(","); + elProperty.checked = subProperties.indexOf(propertyName) > -1; + } else if (elProperty.getAttribute("type") === "number") { + let fixedDecimals = elProperty.getAttribute("fixedDecimals"); + elProperty.value = propertyValue.toFixed(fixedDecimals); + } else if (elProperty.getAttribute("type") === "checkbox") { + let inverse = elProperty.getAttribute("inverse") === "true"; + elProperty.checked = inverse ? !propertyValue : propertyValue; + } else if (elProperty.nodeName === "TEXTAREA") { + elProperty.value = propertyValue; + setTextareaScrolling(elProperty); + } else if (elProperty.nodeName === "SELECT") { // dropdown + elProperty.value = propertyValue; + } else { + elProperty.value = propertyValue; + } + + let propertyShowRules = showPropertyRules[propertyName]; + if (propertyShowRules !== undefined) { + for (let propertyToShow in propertyShowRules) { + let showIfThisPropertyValue = propertyShowRules[propertyToShow]; + let show = String(propertyValue) === String(showIfThisPropertyValue); + let elPropertyToShow = elPropertyElements[propertyToShow]; + if (elPropertyToShow) { + let parentNode = elPropertyToShow.parentNode; + if (parentNode === undefined && elPropertyToShow instanceof Array) { + parentNode = elPropertyToShow[0].parentNode; + } + parentNode.style.display = show ? "block" : "none"; + } + } + } + } + + let elGrabbable = elPropertyElements["grabbable"]; + let elTriggerable = elPropertyElements["triggerable"]; + let elIgnoreIK = elPropertyElements["ignoreIK"]; + elGrabbable.checked = elPropertyElements["dynamic"].checked; + elTriggerable.checked = false; + elIgnoreIK.checked = true; + let grabbablesSet = false; + let parsedUserData = {}; + try { + parsedUserData = JSON.parse(properties.userData); + if ("grabbableKey" in parsedUserData) { + grabbablesSet = true; + let grabbableData = parsedUserData.grabbableKey; + if ("grabbable" in grabbableData) { + elGrabbable.checked = grabbableData.grabbable; + } else { + elGrabbable.checked = true; + } + if ("triggerable" in grabbableData) { + elTriggerable.checked = grabbableData.triggerable; + } else if ("wantsTrigger" in grabbableData) { + elTriggerable.checked = grabbableData.wantsTrigger; + } else { + elTriggerable.checked = false; + } + if ("ignoreIK" in grabbableData) { + elIgnoreIK.checked = grabbableData.ignoreIK; + } else { + elIgnoreIK.checked = true; + } + } + } catch (e) { + // TODO: What should go here? + } + if (!grabbablesSet) { + elGrabbable.checked = true; + elTriggerable.checked = false; + elIgnoreIK.checked = true; + } + + if (properties.type === "Image") { var imageLink = JSON.parse(properties.textures)["tex.picture"]; elPropertyElements["image"].value = imageLink; - } else if (properties.type === "Material") { - let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; - let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; - let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; - if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { + } else if (properties.type === "Material") { + let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; + let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; + let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; + if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { elParentMaterialNameString.value = properties.parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = false; @@ -2356,9 +2341,9 @@ function loaded() { showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = true; } - } - - let json = null; + } + + let json = null; try { json = JSON.parse(properties.userData); } catch (e) { @@ -2368,7 +2353,7 @@ function loaded() { showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); - hideUserDataSaved(); + hideUserDataSaved(); } if (json !== null) { if (editor === null) { @@ -2377,8 +2362,8 @@ function loaded() { setEditorJSON(json); showSaveUserDataButton(); hideUserDataTextArea(); - hideNewJSONEditorButton(); - hideUserDataSaved(); + hideNewJSONEditorButton(); + hideUserDataSaved(); } let materialJson = null; @@ -2391,7 +2376,7 @@ function loaded() { showMaterialDataTextArea(); showNewJSONMaterialEditorButton(); hideSaveMaterialDataButton(); - hideMaterialDataSaved(); + hideMaterialDataSaved(); } if (materialJson !== null) { if (materialEditor === null) { @@ -2401,19 +2386,19 @@ function loaded() { showSaveMaterialDataButton(); hideMaterialDataTextArea(); hideNewJSONMaterialEditorButton(); - hideMaterialDataSaved(); + hideMaterialDataSaved(); } - - if (properties.locked) { - disableProperties(); - elPropertyElements["locked"].removeAttribute('disabled'); - } else { - enableProperties(); - disableSaveUserDataButton(); - disableSaveMaterialDataButton() - } - - var activeElement = document.activeElement; + + if (properties.locked) { + disableProperties(); + elPropertyElements["locked"].removeAttribute('disabled'); + } else { + enableProperties(); + disableSaveUserDataButton(); + disableSaveMaterialDataButton() + } + + var activeElement = document.activeElement; if (doSelectElement && typeof activeElement.select !== "undefined") { activeElement.select(); } @@ -2421,79 +2406,79 @@ function loaded() { } }); } - - // Server Script Status - let elServerScript = elPropertyElements["serverScripts"]; - let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "property"); - let elLabel = document.createElement('label'); - elLabel.setAttribute("for", "property-serverScripts-status"); - elLabel.innerText = "Server Script Status"; - let elServerScriptStatus = document.createElement('span'); - elServerScriptStatus.setAttribute("id", "property-serverScripts-status"); - elDiv.appendChild(elLabel); - elDiv.appendChild(elServerScriptStatus); - elServerScript.parentNode.appendChild(elDiv); - - // Server Script Error - elDiv = document.createElement('div'); - elDiv.setAttribute("class", "property"); - let elServerScriptError = document.createElement('textarea'); - elServerScriptError.setAttribute("id", "property-serverScripts-error"); - elDiv.appendChild(elServerScriptError); - elServerScript.parentNode.appendChild(elDiv); - - let elScript = elPropertyElements["script"]; - elScript.parentNode.setAttribute("class", "property url refresh"); - elServerScript.parentNode.setAttribute("class", "property url refresh"); - - // User Data - let elUserData = elPropertyElements["userData"]; - elDiv = elUserData.parentNode; - let elStaticUserData = document.createElement('div'); - elStaticUserData.setAttribute("id", "property-userData-static"); - let elUserDataEditor = document.createElement('div'); - elUserDataEditor.setAttribute("id", "property-userData-editor"); - let elUserDataSaved = document.createElement('span'); - elUserDataSaved.setAttribute("id", "property-userData-saved"); - elUserDataSaved.innerText = "Saved!"; - elDiv.childNodes[2].appendChild(elUserDataSaved); - elDiv.insertBefore(elStaticUserData, elUserData); - elDiv.insertBefore(elUserDataEditor, elUserData); - - // Material Data - let elMaterialData = elPropertyElements["materialData"]; - elDiv = elMaterialData.parentNode; - let elStaticMaterialData = document.createElement('div'); - elStaticMaterialData.setAttribute("id", "property-materialData-static"); - let elMaterialDataEditor = document.createElement('div'); - elMaterialDataEditor.setAttribute("id", "property-materialData-editor"); - let elMaterialDataSaved = document.createElement('span'); - elMaterialDataSaved.setAttribute("id", "property-materialData-saved"); - elMaterialDataSaved.innerText = "Saved!"; - elDiv.childNodes[2].appendChild(elMaterialDataSaved); - elDiv.insertBefore(elStaticMaterialData, elMaterialData); - elDiv.insertBefore(elMaterialDataEditor, elMaterialData); - - // User Data Fields - let elGrabbable = elPropertyElements["grabbable"]; - let elTriggerable = elPropertyElements["triggerable"]; - let elIgnoreIK = elPropertyElements["ignoreIK"]; - elGrabbable.addEventListener('change', function() { + + // Server Script Status + let elServerScript = elPropertyElements["serverScripts"]; + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "property"); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", "property-serverScripts-status"); + elLabel.innerText = "Server Script Status"; + let elServerScriptStatus = document.createElement('span'); + elServerScriptStatus.setAttribute("id", "property-serverScripts-status"); + elDiv.appendChild(elLabel); + elDiv.appendChild(elServerScriptStatus); + elServerScript.parentNode.appendChild(elDiv); + + // Server Script Error + elDiv = document.createElement('div'); + elDiv.setAttribute("class", "property"); + let elServerScriptError = document.createElement('textarea'); + elServerScriptError.setAttribute("id", "property-serverScripts-error"); + elDiv.appendChild(elServerScriptError); + elServerScript.parentNode.appendChild(elDiv); + + let elScript = elPropertyElements["script"]; + elScript.parentNode.setAttribute("class", "property url refresh"); + elServerScript.parentNode.setAttribute("class", "property url refresh"); + + // User Data + let elUserData = elPropertyElements["userData"]; + elDiv = elUserData.parentNode; + let elStaticUserData = document.createElement('div'); + elStaticUserData.setAttribute("id", "property-userData-static"); + let elUserDataEditor = document.createElement('div'); + elUserDataEditor.setAttribute("id", "property-userData-editor"); + let elUserDataSaved = document.createElement('span'); + elUserDataSaved.setAttribute("id", "property-userData-saved"); + elUserDataSaved.innerText = "Saved!"; + elDiv.childNodes[2].appendChild(elUserDataSaved); + elDiv.insertBefore(elStaticUserData, elUserData); + elDiv.insertBefore(elUserDataEditor, elUserData); + + // Material Data + let elMaterialData = elPropertyElements["materialData"]; + elDiv = elMaterialData.parentNode; + let elStaticMaterialData = document.createElement('div'); + elStaticMaterialData.setAttribute("id", "property-materialData-static"); + let elMaterialDataEditor = document.createElement('div'); + elMaterialDataEditor.setAttribute("id", "property-materialData-editor"); + let elMaterialDataSaved = document.createElement('span'); + elMaterialDataSaved.setAttribute("id", "property-materialData-saved"); + elMaterialDataSaved.innerText = "Saved!"; + elDiv.childNodes[2].appendChild(elMaterialDataSaved); + elDiv.insertBefore(elStaticMaterialData, elMaterialData); + elDiv.insertBefore(elMaterialDataEditor, elMaterialData); + + // User Data Fields + let elGrabbable = elPropertyElements["grabbable"]; + let elTriggerable = elPropertyElements["triggerable"]; + let elIgnoreIK = elPropertyElements["ignoreIK"]; + elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); }); - elTriggerable.addEventListener('change', function() { + elTriggerable.addEventListener('change', function() { userDataChanger("grabbableKey", "triggerable", elTriggerable, elUserData, false, ['wantsTrigger']); }); elIgnoreIK.addEventListener('change', function() { userDataChanger("grabbableKey", "ignoreIK", elIgnoreIK, elUserData, true); }); - - // Special Property Callbacks - let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; - let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; - let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; - elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); + + // Special Property Callbacks + let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; + let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; + let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; + elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); elParentMaterialNameNumber.addEventListener('change', function () { updateProperty("parentMaterialName", this.value); }); elParentMaterialNameCheckbox.addEventListener('change', function () { if (this.checked) { @@ -2504,43 +2489,43 @@ function loaded() { showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); } }); - - elPropertyElements["image"].addEventListener('change', createImageURLUpdateFunction('textures')); - - // Collapsible sections - let elCollapsible = document.getElementsByClassName("section-header"); + + elPropertyElements["image"].addEventListener('change', createImageURLUpdateFunction('textures')); + + // Collapsible sections + let elCollapsible = document.getElementsByClassName("section-header"); - let toggleCollapsedEvent = function(event) { - let element = event.target.parentNode.parentNode; - let isCollapsed = element.dataset.collapsed !== "true"; - element.dataset.collapsed = isCollapsed ? "true" : false; - element.setAttribute("collapsed", isCollapsed ? "true" : "false"); - element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M"; - }; + let toggleCollapsedEvent = function(event) { + let element = event.target.parentNode.parentNode; + let isCollapsed = element.dataset.collapsed !== "true"; + element.dataset.collapsed = isCollapsed ? "true" : false; + element.setAttribute("collapsed", isCollapsed ? "true" : "false"); + element.getElementsByClassName(".collapse-icon")[0].textContent = isCollapsed ? "L" : "M"; + }; - for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { - let curCollapsibleElement = elCollapsible[collapseIndex]; - curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); - } - - // Textarea scrollbars - let elTextareas = document.getElementsByTagName("TEXTAREA"); + for (let collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { + let curCollapsibleElement = elCollapsible[collapseIndex]; + curCollapsibleElement.getElementsByTagName('span')[0].addEventListener("click", toggleCollapsedEvent, true); + } + + // Textarea scrollbars + let elTextareas = document.getElementsByTagName("TEXTAREA"); - let textareaOnChangeEvent = function(event) { - setTextareaScrolling(event.target); - }; + let textareaOnChangeEvent = function(event) { + setTextareaScrolling(event.target); + }; - for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { - let curTextAreaElement = elTextareas[textAreaIndex]; - setTextareaScrolling(curTextAreaElement); - curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); - curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); - /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize - event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ - curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); - } - - document.addEventListener("keydown", function (keyDown) { + for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { + let curTextAreaElement = elTextareas[textAreaIndex]; + setTextareaScrolling(curTextAreaElement); + curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); + curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); + /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize + event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ + curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); + } + + document.addEventListener("keydown", function (keyDown) { if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { if (keyDown.shiftKey) { EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); @@ -2549,28 +2534,28 @@ function loaded() { } } }); - + window.onblur = function() { // Fake a change event let ev = document.createEvent("HTMLEvents"); ev.initEvent("change", true, true); document.activeElement.dispatchEvent(ev); }; - - // For input and textarea elements, select all of the text on focus + + // For input and textarea elements, select all of the text on focus let els = document.querySelectorAll("input, textarea"); for (let i = 0; i < els.length; i++) { els[i].onfocus = function (e) { e.target.select(); }; } - - bindAllNonJSONEditorElements(); + + bindAllNonJSONEditorElements(); - showGroupsForType("None"); - resetProperties(); - disableProperties(); - }); + showGroupsForType("None"); + resetProperties(); + disableProperties(); + }); augmentSpinButtons(); From 8642edd144242f061b493e843ed68d2805c5ddcf Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 13 Sep 2018 18:18:23 -0700 Subject: [PATCH 061/276] Added acceleration limit filter to controller system --- interface/resources/controllers/vive.json | 9 +- .../src/controllers/impl/Filter.cpp | 2 + .../filters/AccelerationLimiterFilter.cpp | 163 ++++++++++++++++++ .../impl/filters/AccelerationLimiterFilter.h | 40 +++++ 4 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp create mode 100644 libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 8a7744efb3..442584a2ce 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -51,8 +51,13 @@ { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, - { "from": "Vive.LeftHand", "to": "Standard.LeftHand"}, - { "from": "Vive.RightHand", "to": "Standard.RightHand"}, + { "from": "Vive.LeftHand", "to": "Standard.LeftHand", + "filters" : [{"type" : "accelerationLimiter", "rotationLimit" : 4000.0, "translationLimit": 200.0}] + }, + + { "from": "Vive.RightHand", "to": "Standard.RightHand", + "filters" : [{"type" : "accelerationLimiter", "rotationLimit" : 2000.0, "translationLimit": 100.0}] + }, { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp index 6e6dc816d0..f230fb83dc 100644 --- a/libraries/controllers/src/controllers/impl/Filter.cpp +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -31,6 +31,7 @@ #include "filters/RotateFilter.h" #include "filters/LowVelocityFilter.h" #include "filters/ExponentialSmoothingFilter.h" +#include "filters/AccelerationLimiterFilter.h" using namespace controller; @@ -51,6 +52,7 @@ REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform") REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate") REGISTER_FILTER_CLASS_INSTANCE(LowVelocityFilter, "lowVelocity") REGISTER_FILTER_CLASS_INSTANCE(ExponentialSmoothingFilter, "exponentialSmoothing") +REGISTER_FILTER_CLASS_INSTANCE(AccelerationLimiterFilter, "accelerationLimiter") const QString JSON_FILTER_TYPE = QStringLiteral("type"); const QString JSON_FILTER_PARAMS = QStringLiteral("params"); diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp new file mode 100644 index 0000000000..1f63b28786 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -0,0 +1,163 @@ +// +// Created by Anthony Thibault 2018/11/09 +// 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 "AccelerationLimiterFilter.h" + +#include +#include +#include "../../UserInputMapper.h" +#include "../../Input.h" +#include +#include +#include + +static const QString JSON_ROTATION_LIMIT = QStringLiteral("rotationLimit"); +static const QString JSON_TRANSLATION_LIMIT = QStringLiteral("translationLimit"); + +static glm::vec3 angularVelFromDeltaRot(const glm::quat& deltaQ, float dt) { + // Measure the angular velocity of a delta rotation quaternion by using quaternion logarithm. + // The logarithm of a unit quternion returns the axis of rotation with a length of one half the angle of rotation in the imaginary part. + // The real part will be 0. Then we multiply it by 2 / dt. turning it into the angular velocity, (except for the extra w = 0 part). + glm::quat omegaQ((2.0f / dt) * glm::log(deltaQ)); + return glm::vec3(omegaQ.x, omegaQ.y, omegaQ.z); +} + +static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { + // Convert angular velocity into a delta quaternion by using quaternion exponent. + // The exponent of quaternion will return a delta rotation around the axis of the imaginary part, by twice the angle as determined by the length of that imaginary part. + // It is the inverse of the logarithm step in angularVelFromDeltaRot + glm::quat omegaQ(0.0f, omega.x, omega.y, omega.z); + return glm::exp((dt / 2.0f) * omegaQ); +} + +static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, float dt, const float aLimit) { + + // measure the linear velocities of this step and the previoius step + glm::vec3 v1 = (x3 - x1) / (2.0f * dt); + glm::vec3 v0 = (x2 - x0) / (2.0f * dt); + + // compute the acceleration + const glm::vec3 a = (v1 - v0) / dt; + + // clamp the acceleration if it is over the limit + float aLen = glm::length(a); + + if (aLen > aLimit) { + // Solve for a new `v1`, such that `a` does not exceed `aLimit` + // This combines two steps: + // 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`: + // `newA = a * (aLimit / aLen)` + // 2) Computing new `v1` + // `v1 = newA * dt + v0` + // We combine the scalars from step 1 and step 2 into a single term to avoid having to do multiple scalar-vec3 multiplies. + v1 = a * ((aLimit * dt) / aLen) + v0; + + // apply limited v1 to compute filtered x3 + return v1 * dt + x2; + } else { + // did not exceed limit, no filtering necesary + return x3; + } +} + +static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, float dt, const float aLimit) { + + // ensure quaternions have the same polarity + glm::quat q0 = q0In; + glm::quat q1 = glm::dot(q0In, q1In) < 0.0f ? -q1In : q1In; + glm::quat q2 = glm::dot(q1In, q2In) < 0.0f ? -q2In : q2In; + glm::quat q3 = glm::dot(q2In, q3In) < 0.0f ? -q3In : q3In; + + // measure the angular velocities of this step and the previous step + glm::vec3 w1 = angularVelFromDeltaRot(q3 * glm::inverse(q1), 2.0f * dt); + glm::vec3 w0 = angularVelFromDeltaRot(q2 * glm::inverse(q0), 2.0f * dt); + + const glm::vec3 a = (w1 - w0) / dt; + + // clamp the acceleration if it is over the limit + float aLen = glm::length(a); + if (aLen > aLimit) { + // solve for a new w1, such that a does not exceed the accLimit + w1 = a * ((aLimit * dt) / aLen) + w0; + + // apply limited w1 to compute filtered q3 + return deltaRotFromAngularVel(w1, dt) * q2; + } else { + // did not exceed limit, no filtering necesary + return q3; + } +} + +namespace controller { + + Pose AccelerationLimiterFilter::apply(Pose value) const { + + if (value.isValid()) { + + // to perform filtering in sensor space, we need to compute the transformations. + auto userInputMapper = DependencyManager::get(); + const InputCalibrationData calibrationData = userInputMapper->getInputCalibrationData(); + glm::mat4 sensorToAvatarMat = glm::inverse(calibrationData.avatarMat) * calibrationData.sensorToWorldMat; + glm::mat4 avatarToSensorMat = glm::inverse(calibrationData.sensorToWorldMat) * calibrationData.avatarMat; + + // transform pose into sensor space. + Pose sensorValue = value.transform(avatarToSensorMat); + + if (_prevValid) { + + const float DELTA_TIME = 0.01111111f; + + sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, DELTA_TIME, _translationLimit); + sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, DELTA_TIME, _rotationLimit); + + // remember previous values. + _prevPos[0] = _prevPos[1]; + _prevPos[1] = _prevPos[2]; + _prevPos[2] = sensorValue.translation; + _prevRot[0] = _prevRot[1]; + _prevRot[1] = _prevRot[2]; + _prevRot[2] = sensorValue.rotation; + + // transform back into avatar space + return sensorValue.transform(sensorToAvatarMat); + } else { + // initialize previous values with the current sample. + _prevPos[0] = sensorValue.translation; + _prevPos[1] = sensorValue.translation; + _prevPos[2] = sensorValue.translation; + _prevRot[0] = sensorValue.rotation; + _prevRot[1] = sensorValue.rotation; + _prevRot[2] = sensorValue.rotation; + _prevValid = true; + + // no previous value to smooth with, so return value unchanged + return value; + } + } else { + // mark previous poses as invalid. + _prevValid = false; + + // return invalid value unchanged + return value; + } + } + + bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) { + if (parameters.isObject()) { + auto obj = parameters.toObject(); + if (obj.contains(JSON_ROTATION_LIMIT) && obj.contains(JSON_TRANSLATION_LIMIT)) { + _rotationLimit = (float)obj[JSON_ROTATION_LIMIT].toDouble(); + _translationLimit = (float)obj[JSON_TRANSLATION_LIMIT].toDouble(); + return true; + } + } + return false; + } + +} diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h new file mode 100644 index 0000000000..22a1ca530b --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -0,0 +1,40 @@ +// +// Created by Anthony Thibault 2018/11/09 +// 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_Controllers_Filters_Acceleration_Limiter_h +#define hifi_Controllers_Filters_Acceleration_Limiter_h + +#include "../Filter.h" + +namespace controller { + + class AccelerationLimiterFilter : public Filter { + REGISTER_FILTER_CLASS(AccelerationLimiterFilter); + + public: + AccelerationLimiterFilter() {} + AccelerationLimiterFilter(float rotationLimit, float translationLimit) : + _rotationLimit(rotationLimit), + _translationLimit(translationLimit) {} + + float apply(float value) const override { return value; } + Pose apply(Pose value) const override; + bool parseParameters(const QJsonValue& parameters) override; + + private: + float _rotationLimit { FLT_MAX }; + float _translationLimit { FLT_MAX }; + + mutable glm::vec3 _prevPos[3]; // sensor space + mutable glm::quat _prevRot[3]; // sensor space + mutable bool _prevValid { false }; + }; + +} + +#endif From d15cc86735cc65a2fafaef4e2cd71040efb07db2 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 18 Sep 2018 11:53:17 -0700 Subject: [PATCH 062/276] Added deceleration limit to AccelerationLimiterFilter --- interface/resources/controllers/vive.json | 8 +++- .../filters/AccelerationLimiterFilter.cpp | 41 ++++++++++++------- .../impl/filters/AccelerationLimiterFilter.h | 9 ++-- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 442584a2ce..e737fec594 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -52,11 +52,15 @@ { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, { "from": "Vive.LeftHand", "to": "Standard.LeftHand", - "filters" : [{"type" : "accelerationLimiter", "rotationLimit" : 4000.0, "translationLimit": 200.0}] + "filters" : [{"type" : "accelerationLimiter", + "rotationAccelerationLimit" : 4000.0, "rotationDecelerationLimit" : 8000.0, + "translationAccelerationLimit": 200.0, "translationDecelerationLimit": 400.0}] }, { "from": "Vive.RightHand", "to": "Standard.RightHand", - "filters" : [{"type" : "accelerationLimiter", "rotationLimit" : 2000.0, "translationLimit": 100.0}] + "filters" : [{"type" : "accelerationLimiter", + "rotationAccelerationLimit" : 2000.0, "rotationDecelerationLimit" : 4000.0, + "translationAccelerationLimit": 100.0, "translationDecelerationLimit": 200.0}] }, { diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp index 1f63b28786..234dff1c65 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -17,8 +17,10 @@ #include #include -static const QString JSON_ROTATION_LIMIT = QStringLiteral("rotationLimit"); -static const QString JSON_TRANSLATION_LIMIT = QStringLiteral("translationLimit"); +static const QString JSON_ROTATION_ACCELERATION_LIMIT = QStringLiteral("rotationAccelerationLimit"); +static const QString JSON_ROTATION_DECELERATION_LIMIT = QStringLiteral("rotationDecelerationLimit"); +static const QString JSON_TRANSLATION_ACCELERATION_LIMIT = QStringLiteral("translationAccelerationLimit"); +static const QString JSON_TRANSLATION_DECELERATION_LIMIT = QStringLiteral("translationDecelerationLimit"); static glm::vec3 angularVelFromDeltaRot(const glm::quat& deltaQ, float dt) { // Measure the angular velocity of a delta rotation quaternion by using quaternion logarithm. @@ -36,7 +38,7 @@ static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { return glm::exp((dt / 2.0f) * omegaQ); } -static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, float dt, const float aLimit) { +static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, float dt, const float accLimit, const float decLimit) { // measure the linear velocities of this step and the previoius step glm::vec3 v1 = (x3 - x1) / (2.0f * dt); @@ -48,7 +50,10 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con // clamp the acceleration if it is over the limit float aLen = glm::length(a); - if (aLen > aLimit) { + // pick limit based on if we are accelerating or decelerating. + float limit = glm::length(v1) > glm::length(v0) ? accLimit : decLimit; + + if (aLen > limit) { // Solve for a new `v1`, such that `a` does not exceed `aLimit` // This combines two steps: // 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`: @@ -56,7 +61,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con // 2) Computing new `v1` // `v1 = newA * dt + v0` // We combine the scalars from step 1 and step 2 into a single term to avoid having to do multiple scalar-vec3 multiplies. - v1 = a * ((aLimit * dt) / aLen) + v0; + v1 = a * ((limit * dt) / aLen) + v0; // apply limited v1 to compute filtered x3 return v1 * dt + x2; @@ -66,7 +71,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con } } -static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, float dt, const float aLimit) { +static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, float dt, const float accLimit, const float decLimit) { // ensure quaternions have the same polarity glm::quat q0 = q0In; @@ -79,12 +84,15 @@ static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, co glm::vec3 w0 = angularVelFromDeltaRot(q2 * glm::inverse(q0), 2.0f * dt); const glm::vec3 a = (w1 - w0) / dt; + float aLen = glm::length(a); + + // pick limit based on if we are accelerating or decelerating. + float limit = glm::length(w1) > glm::length(w0) ? accLimit : decLimit; // clamp the acceleration if it is over the limit - float aLen = glm::length(a); - if (aLen > aLimit) { + if (aLen > limit) { // solve for a new w1, such that a does not exceed the accLimit - w1 = a * ((aLimit * dt) / aLen) + w0; + w1 = a * ((limit * dt) / aLen) + w0; // apply limited w1 to compute filtered q3 return deltaRotFromAngularVel(w1, dt) * q2; @@ -113,8 +121,10 @@ namespace controller { const float DELTA_TIME = 0.01111111f; - sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, DELTA_TIME, _translationLimit); - sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, DELTA_TIME, _rotationLimit); + sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, + DELTA_TIME, _translationAccelerationLimit, _translationDecelerationLimit); + sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, + DELTA_TIME, _rotationAccelerationLimit, _rotationDecelerationLimit); // remember previous values. _prevPos[0] = _prevPos[1]; @@ -151,9 +161,12 @@ namespace controller { bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) { if (parameters.isObject()) { auto obj = parameters.toObject(); - if (obj.contains(JSON_ROTATION_LIMIT) && obj.contains(JSON_TRANSLATION_LIMIT)) { - _rotationLimit = (float)obj[JSON_ROTATION_LIMIT].toDouble(); - _translationLimit = (float)obj[JSON_TRANSLATION_LIMIT].toDouble(); + if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_ROTATION_DECELERATION_LIMIT) && + obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_DECELERATION_LIMIT)) { + _rotationAccelerationLimit = (float)obj[JSON_ROTATION_ACCELERATION_LIMIT].toDouble(); + _rotationDecelerationLimit = (float)obj[JSON_ROTATION_DECELERATION_LIMIT].toDouble(); + _translationAccelerationLimit = (float)obj[JSON_TRANSLATION_ACCELERATION_LIMIT].toDouble(); + _translationDecelerationLimit = (float)obj[JSON_TRANSLATION_DECELERATION_LIMIT].toDouble(); return true; } } diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h index 22a1ca530b..6a6c7f8c33 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -18,17 +18,16 @@ namespace controller { public: AccelerationLimiterFilter() {} - AccelerationLimiterFilter(float rotationLimit, float translationLimit) : - _rotationLimit(rotationLimit), - _translationLimit(translationLimit) {} float apply(float value) const override { return value; } Pose apply(Pose value) const override; bool parseParameters(const QJsonValue& parameters) override; private: - float _rotationLimit { FLT_MAX }; - float _translationLimit { FLT_MAX }; + float _rotationAccelerationLimit { FLT_MAX }; + float _rotationDecelerationLimit { FLT_MAX }; + float _translationAccelerationLimit { FLT_MAX }; + float _translationDecelerationLimit { FLT_MAX }; mutable glm::vec3 _prevPos[3]; // sensor space mutable glm::quat _prevRot[3]; // sensor space From 7777f3edd0233f8850a25edc9c963ce4517c4c7f Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 19 Sep 2018 16:08:16 -0700 Subject: [PATCH 063/276] Added OutOfRangeDataStrategy parameter to Controller Settings The openvr SDK provides a way to gauge the quality of tracking on a given device via the eTrackingResult enum. * Drop - Only Running_OK is considered valid, all other eTrackingResults will return invalid poses. * Freeze - Only Running_OK is considered valid, but other valid TrackingResults will return the last Running_OK pose. In esseces this will freeze the puck in place at the last good value. * None - All valid eTrackingResults will be valid, including OutOfRange and Calibrating results. This is the default. --- interface/resources/controllers/vive.json | 13 +-- .../qml/hifi/tablet/OpenVrConfiguration.qml | 41 +++++++- plugins/openvr/src/ViveControllerManager.cpp | 95 ++++++++++++++----- plugins/openvr/src/ViveControllerManager.h | 8 ++ 4 files changed, 118 insertions(+), 39 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index e737fec594..8a7744efb3 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -51,17 +51,8 @@ { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, - { "from": "Vive.LeftHand", "to": "Standard.LeftHand", - "filters" : [{"type" : "accelerationLimiter", - "rotationAccelerationLimit" : 4000.0, "rotationDecelerationLimit" : 8000.0, - "translationAccelerationLimit": 200.0, "translationDecelerationLimit": 400.0}] - }, - - { "from": "Vive.RightHand", "to": "Standard.RightHand", - "filters" : [{"type" : "accelerationLimiter", - "rotationAccelerationLimit" : 2000.0, "rotationDecelerationLimit" : 4000.0, - "translationAccelerationLimit": 100.0, "translationDecelerationLimit": 200.0}] - }, + { "from": "Vive.LeftHand", "to": "Standard.LeftHand"}, + { "from": "Vive.RightHand", "to": "Standard.RightHand"}, { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index c2aff08e35..f91642105f 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -822,11 +822,44 @@ Flickable { } } + Row { + id: outOfRangeDataStrategyRow + anchors.top: viveInDesktop.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 15 + + RalewayRegular { + id: outOfRangeDataStrategyLabel + size: 12 + text: "Out Of Range Data Strategy:" + color: hifi.colors.lightGrayText + topPadding: 5 + } + + HifiControls.ComboBox { + id: outOfRangeDataStrategyComboBox + + height: 25 + width: 100 + + editable: true + colorScheme: hifi.colorSchemes.dark + model: ["None", "Freeze", "Drop"] + label: "" + + onCurrentIndexChanged: { + sendConfigurationSettings(); + } + } + } + RalewayBold { id: viveDesktopText - size: 10 + size: 12 text: "Use " + stack.selectedPlugin + " devices in desktop mode" - color: hifi.colors.white + color: hifi.colors.lightGrayText anchors { left: viveInDesktop.right @@ -946,6 +979,7 @@ Flickable { viveInDesktop.checked = desktopMode; hmdInDesktop.checked = hmdDesktopPosition; + outOfRangeDataStrategyComboBox.currentIndex = outOfRangeDataStrategyComboBox.model.indexOf(settings.outOfRangeDataStrategy); initializeButtonState(); updateCalibrationText(); @@ -1107,7 +1141,8 @@ Flickable { "armCircumference": armCircumference.realValue, "shoulderWidth": shoulderWidth.realValue, "desktopMode": viveInDesktop.checked, - "hmdDesktopTracking": hmdInDesktop.checked + "hmdDesktopTracking": hmdInDesktop.checked, + "outOfRangeDataStrategy": outOfRangeDataStrategyComboBox.model[outOfRangeDataStrategyComboBox.currentIndex] } return settingsObject; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 3e26f304f8..cc3ca523df 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -129,6 +129,28 @@ static glm::mat4 calculateResetMat() { return glm::mat4(); } +static QString outOfRangeDataStrategyToString(ViveControllerManager::OutOfRangeDataStrategy strategy) { + switch (strategy) { + default: + case ViveControllerManager::OutOfRangeDataStrategy::None: + return "None"; + case ViveControllerManager::OutOfRangeDataStrategy::Freeze: + return "Freeze"; + case ViveControllerManager::OutOfRangeDataStrategy::Drop: + return "Drop"; + } +} + +static ViveControllerManager::OutOfRangeDataStrategy stringToOutOfRangeDataStrategy(const QString& string) { + if (string == "Drop") { + return ViveControllerManager::OutOfRangeDataStrategy::Drop; + } else if (string == "Freeze") { + return ViveControllerManager::OutOfRangeDataStrategy::Freeze; + } else { + return ViveControllerManager::OutOfRangeDataStrategy::None; + } +} + bool ViveControllerManager::isDesktopMode() { if (_container) { return !_container->getActiveDisplayPlugin()->isHmd(); @@ -288,8 +310,10 @@ void ViveControllerManager::loadSettings() { if (_inputDevice) { const double DEFAULT_ARM_CIRCUMFERENCE = 0.33; const double DEFAULT_SHOULDER_WIDTH = 0.48; + const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "None"; _inputDevice->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble(); _inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble(); + _inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString()); } } settings.endGroup(); @@ -303,6 +327,7 @@ void ViveControllerManager::saveSettings() const { if (_inputDevice) { settings.setValue(QString("armCircumference"), _inputDevice->_armCircumference); settings.setValue(QString("shoulderWidth"), _inputDevice->_shoulderWidth); + settings.setValue(QString("outOfRangeDataStrategy"), outOfRangeDataStrategyToString(_inputDevice->_outOfRangeDataStrategy)); } } settings.endGroup(); @@ -446,6 +471,8 @@ void ViveControllerManager::InputDevice::configureCalibrationSettings(const QJso hmdDesktopTracking = iter.value().toBool(); } else if (iter.key() == "desktopMode") { hmdDesktopMode = iter.value().toBool(); + } else if (iter.key() == "outOfRangeDataStrategy") { + _outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(iter.value().toString()); } iter++; } @@ -468,6 +495,7 @@ QJsonObject ViveControllerManager::InputDevice::configurationSettings() { configurationSettings["puckCount"] = (int)_validTrackedObjects.size(); configurationSettings["armCircumference"] = (double)_armCircumference * M_TO_CM; configurationSettings["shoulderWidth"] = (double)_shoulderWidth * M_TO_CM; + configurationSettings["outOfRangeDataStrategy"] = outOfRangeDataStrategyToString(_outOfRangeDataStrategy); return configurationSettings; } @@ -484,6 +512,10 @@ void ViveControllerManager::InputDevice::emitCalibrationStatus() { emit inputConfiguration->calibrationStatus(status); } +static controller::Pose buildPose(const glm::mat4& mat, const glm::vec3& linearVelocity, const glm::vec3& angularVelocity) { + return controller::Pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity); +} + void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) { uint32_t poseIndex = controller::TRACKED_OBJECT_00 + deviceIndex; printDeviceTrackingResultChange(deviceIndex); @@ -492,35 +524,48 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid && poseIndex <= controller::TRACKED_OBJECT_15) { - mat4& mat = mat4(); - vec3 linearVelocity = vec3(); - vec3 angularVelocity = vec3(); - // check if the device is tracking out of range, then process the correct pose depending on the result. - if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult != vr::TrackingResult_Running_OutOfRange) { - mat = _nextSimPoseData.poses[deviceIndex]; - linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex]; - angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex]; - } else { - mat = _lastSimPoseData.poses[deviceIndex]; - linearVelocity = _lastSimPoseData.linearVelocities[deviceIndex]; - angularVelocity = _lastSimPoseData.angularVelocities[deviceIndex]; - - // make sure that we do not overwrite the pose in the _lastSimPose with incorrect data. - _nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex]; - _nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex]; - _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; + controller::Pose pose; + switch (_outOfRangeDataStrategy) { + case OutOfRangeDataStrategy::Drop: + default: + // Drop - Mark all non Running_OK results as invald + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) { + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + } else { + pose.valid = false; + } + break; + case OutOfRangeDataStrategy::None: + // None - Ignore eTrackingResult all together + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + break; + case OutOfRangeDataStrategy::Freeze: + // Freeze - Dont invalide non Running_OK poses, instead just return the last good pose. + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) { + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + } else { + pose = buildPose(_lastSimPoseData.poses[deviceIndex], _lastSimPoseData.linearVelocities[deviceIndex], _lastSimPoseData.angularVelocities[deviceIndex]); + // make sure that we do not overwrite the pose in the _lastSimPose with incorrect data. + _nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex]; + _nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex]; + _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; + } + break; } - controller::Pose pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity); + if (pose.valid) { + // transform into avatar frame + glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; + _poseStateMap[poseIndex] = pose.transform(controllerToAvatar); - // transform into avatar frame - glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; - _poseStateMap[poseIndex] = pose.transform(controllerToAvatar); - - // but _validTrackedObjects remain in sensor frame - _validTrackedObjects.push_back(std::make_pair(poseIndex, pose)); - _trackedControllers++; + // but _validTrackedObjects remain in sensor frame + _validTrackedObjects.push_back(std::make_pair(poseIndex, pose)); + _trackedControllers++; + } else { + // insert invalid pose into state map + _poseStateMap[poseIndex] = pose; + } } else { controller::Pose invalidPose; _poseStateMap[poseIndex] = invalidPose; diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 30f8590062..f59ed9d62a 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -60,11 +60,18 @@ public: virtual void saveSettings() const override; virtual void loadSettings() override; + enum class OutOfRangeDataStrategy { + None, + Freeze, + Drop + }; + private: class InputDevice : public controller::InputDevice { public: InputDevice(vr::IVRSystem*& system); bool isHeadControllerMounted() const { return _overrideHead; } + private: // Device functions controller::Input::NamedVector getAvailableInputs() const override; @@ -162,6 +169,7 @@ private: FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; std::string _headsetName {""}; + OutOfRangeDataStrategy _outOfRangeDataStrategy { OutOfRangeDataStrategy::None }; std::vector _validTrackedObjects; std::map _pucksPostOffset; From 527c0d41952336c23428f2ddfb534072fdf95ac5 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 20 Sep 2018 11:34:15 -0700 Subject: [PATCH 064/276] Added accelerationFitlerApp. Attempts to make deceleration limit filter work better. --- .../filters/AccelerationLimiterFilter.cpp | 38 +++- .../impl/filters/AccelerationLimiterFilter.h | 2 + scripts/developer/accelerationFilterApp.js | 214 ++++++++++++++++++ 3 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 scripts/developer/accelerationFilterApp.js diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp index 234dff1c65..677e0af75a 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -38,7 +38,8 @@ static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { return glm::exp((dt / 2.0f) * omegaQ); } -static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, float dt, const float accLimit, const float decLimit) { +static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, + const glm::vec3& unfilteredVelocity, float dt, const float accLimit, const float decLimit) { // measure the linear velocities of this step and the previoius step glm::vec3 v1 = (x3 - x1) / (2.0f * dt); @@ -50,8 +51,8 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con // clamp the acceleration if it is over the limit float aLen = glm::length(a); - // pick limit based on if we are accelerating or decelerating. - float limit = glm::length(v1) > glm::length(v0) ? accLimit : decLimit; + // pick limit based on if we are moving faster then our target + float limit = glm::length(v1) > glm::length(unfilteredVelocity) ? accLimit : decLimit; if (aLen > limit) { // Solve for a new `v1`, such that `a` does not exceed `aLimit` @@ -71,7 +72,8 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con } } -static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, float dt, const float accLimit, const float decLimit) { +static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, + const glm::vec3& unfilteredVelocity, float dt, const float accLimit, const float decLimit) { // ensure quaternions have the same polarity glm::quat q0 = q0In; @@ -86,8 +88,8 @@ static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, co const glm::vec3 a = (w1 - w0) / dt; float aLen = glm::length(a); - // pick limit based on if we are accelerating or decelerating. - float limit = glm::length(w1) > glm::length(w0) ? accLimit : decLimit; + // pick limit based on if we are moving faster then our target + float limit = glm::length(w1) > glm::length(unfilteredVelocity) ? accLimit : decLimit; // clamp the acceleration if it is over the limit if (aLen > limit) { @@ -121,9 +123,14 @@ namespace controller { const float DELTA_TIME = 0.01111111f; - sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, + glm::vec3 unfilteredTranslation = sensorValue.translation; + glm::vec3 unfilteredLinearVel = (unfilteredTranslation - _unfilteredPrevPos[1]) / (2.0f * DELTA_TIME); + sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, unfilteredLinearVel, DELTA_TIME, _translationAccelerationLimit, _translationDecelerationLimit); - sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, + glm::quat unfilteredRot = sensorValue.rotation; + glm::quat unfilteredPrevRot = glm::dot(unfilteredRot, _unfilteredPrevRot[1]) < 0.0f ? -_unfilteredPrevRot[1] : _unfilteredPrevRot[1]; + glm::vec3 unfilteredAngularVel = angularVelFromDeltaRot(unfilteredRot * glm::inverse(unfilteredPrevRot), 2.0f * DELTA_TIME); + sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, unfilteredAngularVel, DELTA_TIME, _rotationAccelerationLimit, _rotationDecelerationLimit); // remember previous values. @@ -134,6 +141,13 @@ namespace controller { _prevRot[1] = _prevRot[2]; _prevRot[2] = sensorValue.rotation; + _unfilteredPrevPos[0] = _unfilteredPrevPos[1]; + _unfilteredPrevPos[1] = _unfilteredPrevPos[2]; + _unfilteredPrevPos[2] = unfilteredTranslation; + _unfilteredPrevRot[0] = _unfilteredPrevRot[1]; + _unfilteredPrevRot[1] = _unfilteredPrevRot[2]; + _unfilteredPrevRot[2] = unfilteredRot; + // transform back into avatar space return sensorValue.transform(sensorToAvatarMat); } else { @@ -144,6 +158,14 @@ namespace controller { _prevRot[0] = sensorValue.rotation; _prevRot[1] = sensorValue.rotation; _prevRot[2] = sensorValue.rotation; + + _unfilteredPrevPos[0] = sensorValue.translation; + _unfilteredPrevPos[1] = sensorValue.translation; + _unfilteredPrevPos[2] = sensorValue.translation; + _unfilteredPrevRot[0] = sensorValue.rotation; + _unfilteredPrevRot[1] = sensorValue.rotation; + _unfilteredPrevRot[2] = sensorValue.rotation; + _prevValid = true; // no previous value to smooth with, so return value unchanged diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h index 6a6c7f8c33..06eeef1579 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -31,6 +31,8 @@ namespace controller { mutable glm::vec3 _prevPos[3]; // sensor space mutable glm::quat _prevRot[3]; // sensor space + mutable glm::vec3 _unfilteredPrevPos[3]; // sensor space + mutable glm::quat _unfilteredPrevRot[3]; // sensor space mutable bool _prevValid { false }; }; diff --git a/scripts/developer/accelerationFilterApp.js b/scripts/developer/accelerationFilterApp.js new file mode 100644 index 0000000000..52109c0f5d --- /dev/null +++ b/scripts/developer/accelerationFilterApp.js @@ -0,0 +1,214 @@ +var LEFT_HAND_INDEX = 0; +var RIGHT_HAND_INDEX = 1; +var LEFT_FOOT_INDEX = 2; +var RIGHT_FOOT_INDEX = 3; +var HIPS_INDEX = 4; +var SPINE2_INDEX = 5; + +var mappingJson = { + name: "com.highfidelity.testing.accelerationTest", + channels: [ + { + from: "Vive.LeftHand", + to: "Standard.LeftHand", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + rotationDecelerationLimit: 4000.0, + translationAccelerationLimit: 100.0, + translationDecelerationLimit: 200.0 + } + ] + }, + { + from: "Vive.RightHand", + to: "Standard.RightHand", + filters: [ + { + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + rotationDecelerationLimit: 4000.0, + translationAccelerationLimit: 100.0, + translationDecelerationLimit: 200.0 + } + ] + }, + { + from: "Vive.LeftFoot", + to: "Standard.LeftFoot", + filters: [ + { + type: "exponentialSmoothing", + rotation: 0.15, + translation: 0.3 + } + ] + }, + { + from: "Vive.RightFoot", + to: "Standard.RightFoot", + filters: [ + { + type: "exponentialSmoothing", + rotation: 0.15, + translation: 0.3 + } + ] + }, + { + from: "Vive.Hips", + to: "Standard.Hips", + filters: [ + { + type: "exponentialSmoothing", + rotation: 0.15, + translation: 0.3 + } + ] + }, + { + from: "Vive.Spine2", + to: "Standard.Spine2", + filters: [ + { + type: "exponentialSmoothing", + rotation: 0.15, + translation: 0.3 + } + ] + } + ] +}; + +// +// tablet app boiler plate +// + +var TABLET_BUTTON_NAME = "ACCFILT"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/accelerationFilterApp.html"; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg" +}); + +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(HTML_URL); + } +}); + +var shown = false; + +function onScreenChanged(type, url) { + if (type === "Web" && url === HTML_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + shownChanged(true); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + shownChanged(false); + } + shown = false; + } +} + +function getTranslationAccelerationLimit(i) { + return mappingJson.channels[i].filters[0].translationAccelerationLimit; +} +function setTranslationAccelerationLimit(i, value) { + mappingJson.channels[i].filters[0].translationAccelerationLimit = value; + mappingChanged(); +} +function getTranslationDecelerationLimit(i) { + return mappingJson.channels[i].filters[0].translationDecelerationLimit; +} +function setTranslationDecelerationLimit(i, value) { + mappingJson.channels[i].filters[0].translationDecelerationLimit = value; mappingChanged(); +} +function getRotationAccelerationLimit(i) { + return mappingJson.channels[i].filters[0].rotationAccelerationLimit; +} +function setRotationAccelerationLimit(i, value) { + mappingJson.channels[i].filters[0].rotationAccelerationLimit = value; mappingChanged(); +} +function getRotationDecelerationLimit(i) { + return mappingJson.channels[i].filters[0].rotationDecelerationLimit; +} +function setRotationDecelerationLimit(i, value) { + mappingJson.channels[i].filters[0].rotationDecelerationLimit = value; mappingChanged(); +} + +function onWebEventReceived(msg) { + if (msg.name === "init-complete") { + var values = [ + {name: "left-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-translation-deceleration-limit", val: getTranslationDecelerationLimit(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-rotation-deceleration-limit", val: getRotationDecelerationLimit(LEFT_HAND_INDEX), checked: false} + ]; + tablet.emitScriptEvent(JSON.stringify(values)); + } else if (msg.name === "left-hand-translation-acceleration-limit") { + setTranslationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-hand-translation-deceleration-limit") { + setTranslationDecelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-hand-rotation-acceleration-limit") { + setRotationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-hand-rotation-deceleration-limit") { + setRotationDecelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } +} + +tablet.screenChanged.connect(onScreenChanged); + +function shutdownTabletApp() { + tablet.removeButton(tabletButton); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); +} + +// +// end tablet app boiler plate +// + +var mapping; +function mappingChanged() { + if (mapping) { + mapping.disable(); + } + mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + mapping.enable(); +} + +function shownChanged(newShown) { + if (newShown) { + mappingChanged(); + } else { + mapping.disable(); + } +} + +mappingChanged(); + +Script.scriptEnding.connect(function() { + if (mapping) { + mapping.disable(); + } + tablet.removeButton(tabletButton); +}); + From 8c8b30c0f9e16ae89139e5670741cd728dc80dd9 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 20 Sep 2018 13:03:17 -0700 Subject: [PATCH 065/276] Removed deceleration updated app --- .../filters/AccelerationLimiterFilter.cpp | 33 ++---- scripts/developer/accelerationFilterApp.js | 102 ++++++++++-------- 2 files changed, 66 insertions(+), 69 deletions(-) diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp index 677e0af75a..aacbdd2cea 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -39,7 +39,7 @@ static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { } static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, - const glm::vec3& unfilteredVelocity, float dt, const float accLimit, const float decLimit) { + float dt, const float accLimit) { // measure the linear velocities of this step and the previoius step glm::vec3 v1 = (x3 - x1) / (2.0f * dt); @@ -52,9 +52,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con float aLen = glm::length(a); // pick limit based on if we are moving faster then our target - float limit = glm::length(v1) > glm::length(unfilteredVelocity) ? accLimit : decLimit; - - if (aLen > limit) { + if (aLen > accLimit) { // Solve for a new `v1`, such that `a` does not exceed `aLimit` // This combines two steps: // 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`: @@ -62,7 +60,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con // 2) Computing new `v1` // `v1 = newA * dt + v0` // We combine the scalars from step 1 and step 2 into a single term to avoid having to do multiple scalar-vec3 multiplies. - v1 = a * ((limit * dt) / aLen) + v0; + v1 = a * ((accLimit * dt) / aLen) + v0; // apply limited v1 to compute filtered x3 return v1 * dt + x2; @@ -73,7 +71,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con } static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, - const glm::vec3& unfilteredVelocity, float dt, const float accLimit, const float decLimit) { + float dt, const float accLimit) { // ensure quaternions have the same polarity glm::quat q0 = q0In; @@ -88,13 +86,10 @@ static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, co const glm::vec3 a = (w1 - w0) / dt; float aLen = glm::length(a); - // pick limit based on if we are moving faster then our target - float limit = glm::length(w1) > glm::length(unfilteredVelocity) ? accLimit : decLimit; - // clamp the acceleration if it is over the limit - if (aLen > limit) { + if (aLen > accLimit) { // solve for a new w1, such that a does not exceed the accLimit - w1 = a * ((limit * dt) / aLen) + w0; + w1 = a * ((accLimit * dt) / aLen) + w0; // apply limited w1 to compute filtered q3 return deltaRotFromAngularVel(w1, dt) * q2; @@ -124,14 +119,11 @@ namespace controller { const float DELTA_TIME = 0.01111111f; glm::vec3 unfilteredTranslation = sensorValue.translation; - glm::vec3 unfilteredLinearVel = (unfilteredTranslation - _unfilteredPrevPos[1]) / (2.0f * DELTA_TIME); - sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, unfilteredLinearVel, - DELTA_TIME, _translationAccelerationLimit, _translationDecelerationLimit); + sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, + DELTA_TIME, _translationAccelerationLimit); glm::quat unfilteredRot = sensorValue.rotation; - glm::quat unfilteredPrevRot = glm::dot(unfilteredRot, _unfilteredPrevRot[1]) < 0.0f ? -_unfilteredPrevRot[1] : _unfilteredPrevRot[1]; - glm::vec3 unfilteredAngularVel = angularVelFromDeltaRot(unfilteredRot * glm::inverse(unfilteredPrevRot), 2.0f * DELTA_TIME); - sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, unfilteredAngularVel, - DELTA_TIME, _rotationAccelerationLimit, _rotationDecelerationLimit); + sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, + DELTA_TIME, _rotationAccelerationLimit); // remember previous values. _prevPos[0] = _prevPos[1]; @@ -183,12 +175,9 @@ namespace controller { bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) { if (parameters.isObject()) { auto obj = parameters.toObject(); - if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_ROTATION_DECELERATION_LIMIT) && - obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_DECELERATION_LIMIT)) { + if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT)) { _rotationAccelerationLimit = (float)obj[JSON_ROTATION_ACCELERATION_LIMIT].toDouble(); - _rotationDecelerationLimit = (float)obj[JSON_ROTATION_DECELERATION_LIMIT].toDouble(); _translationAccelerationLimit = (float)obj[JSON_TRANSLATION_ACCELERATION_LIMIT].toDouble(); - _translationDecelerationLimit = (float)obj[JSON_TRANSLATION_DECELERATION_LIMIT].toDouble(); return true; } } diff --git a/scripts/developer/accelerationFilterApp.js b/scripts/developer/accelerationFilterApp.js index 52109c0f5d..a2ef937e52 100644 --- a/scripts/developer/accelerationFilterApp.js +++ b/scripts/developer/accelerationFilterApp.js @@ -9,72 +9,68 @@ var mappingJson = { name: "com.highfidelity.testing.accelerationTest", channels: [ { - from: "Vive.LeftHand", - to: "Standard.LeftHand", + from: "Standard.LeftHand", + to: "Actions.LeftHand", filters: [ { type: "accelerationLimiter", rotationAccelerationLimit: 2000.0, - rotationDecelerationLimit: 4000.0, translationAccelerationLimit: 100.0, - translationDecelerationLimit: 200.0 } ] }, { - from: "Vive.RightHand", - to: "Standard.RightHand", + from: "Standard.RightHand", + to: "Actions.RightHand", filters: [ { type: "accelerationLimiter", rotationAccelerationLimit: 2000.0, - rotationDecelerationLimit: 4000.0, translationAccelerationLimit: 100.0, - translationDecelerationLimit: 200.0 } ] }, { - from: "Vive.LeftFoot", - to: "Standard.LeftFoot", + from: "Standard.LeftFoot", + to: "Actions.LeftFoot", filters: [ { - type: "exponentialSmoothing", - rotation: 0.15, - translation: 0.3 + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, } ] }, { - from: "Vive.RightFoot", - to: "Standard.RightFoot", + from: "Standard.RightFoot", + to: "Actions.RightFoot", filters: [ { - type: "exponentialSmoothing", - rotation: 0.15, - translation: 0.3 + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, } ] }, { - from: "Vive.Hips", - to: "Standard.Hips", + from: "Standard.Hips", + to: "Actions.Hips", filters: [ { - type: "exponentialSmoothing", - rotation: 0.15, - translation: 0.3 + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, } ] }, { - from: "Vive.Spine2", - to: "Standard.Spine2", + from: "Standard.Spine2", + to: "Actions.Spine2", filters: [ { - type: "exponentialSmoothing", - rotation: 0.15, - translation: 0.3 + type: "accelerationLimiter", + rotationAccelerationLimit: 2000.0, + translationAccelerationLimit: 100.0, } ] } @@ -86,7 +82,7 @@ var mappingJson = { // var TABLET_BUTTON_NAME = "ACCFILT"; -var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/accelerationFilterApp.html"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/accelerationFilterApp.html?2"; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var tabletButton = tablet.addButton({ @@ -132,42 +128,54 @@ function setTranslationAccelerationLimit(i, value) { mappingJson.channels[i].filters[0].translationAccelerationLimit = value; mappingChanged(); } -function getTranslationDecelerationLimit(i) { - return mappingJson.channels[i].filters[0].translationDecelerationLimit; -} -function setTranslationDecelerationLimit(i, value) { - mappingJson.channels[i].filters[0].translationDecelerationLimit = value; mappingChanged(); -} function getRotationAccelerationLimit(i) { return mappingJson.channels[i].filters[0].rotationAccelerationLimit; } function setRotationAccelerationLimit(i, value) { mappingJson.channels[i].filters[0].rotationAccelerationLimit = value; mappingChanged(); } -function getRotationDecelerationLimit(i) { - return mappingJson.channels[i].filters[0].rotationDecelerationLimit; -} -function setRotationDecelerationLimit(i, value) { - mappingJson.channels[i].filters[0].rotationDecelerationLimit = value; mappingChanged(); -} function onWebEventReceived(msg) { if (msg.name === "init-complete") { var values = [ {name: "left-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, - {name: "left-hand-translation-deceleration-limit", val: getTranslationDecelerationLimit(LEFT_HAND_INDEX), checked: false}, {name: "left-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_HAND_INDEX), checked: false}, - {name: "left-hand-rotation-deceleration-limit", val: getRotationDecelerationLimit(LEFT_HAND_INDEX), checked: false} + {name: "right-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(RIGHT_HAND_INDEX), checked: false}, + {name: "right-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(RIGHT_HAND_INDEX), checked: false}, + {name: "left-foot-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_FOOT_INDEX), checked: false}, + {name: "left-foot-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_FOOT_INDEX), checked: false}, + {name: "right-foot-translation-acceleration-limit", val: getTranslationAccelerationLimit(RIGHT_FOOT_INDEX), checked: false}, + {name: "right-foot-rotation-acceleration-limit", val: getRotationAccelerationLimit(RIGHT_FOOT_INDEX), checked: false}, + {name: "hips-translation-acceleration-limit", val: getTranslationAccelerationLimit(HIPS_INDEX), checked: false}, + {name: "hips-rotation-acceleration-limit", val: getRotationAccelerationLimit(HIPS_INDEX), checked: false}, + {name: "spine2-translation-acceleration-limit", val: getTranslationAccelerationLimit(SPINE2_INDEX), checked: false}, + {name: "spine2-rotation-acceleration-limit", val: getRotationAccelerationLimit(SPINE2_INDEX), checked: false} ]; tablet.emitScriptEvent(JSON.stringify(values)); } else if (msg.name === "left-hand-translation-acceleration-limit") { setTranslationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); - } else if (msg.name === "left-hand-translation-deceleration-limit") { - setTranslationDecelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); } else if (msg.name === "left-hand-rotation-acceleration-limit") { setRotationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); - } else if (msg.name === "left-hand-rotation-deceleration-limit") { - setRotationDecelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-hand-translation-acceleration-limit") { + setTranslationAccelerationLimit(RIGHT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-hand-rotation-acceleration-limit") { + setRotationAccelerationLimit(RIGHT_HAND_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-foot-translation-acceleration-limit") { + setTranslationAccelerationLimit(LEFT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "left-foot-rotation-acceleration-limit") { + setRotationAccelerationLimit(LEFT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-foot-translation-acceleration-limit") { + setTranslationAccelerationLimit(RIGHT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "right-foot-rotation-acceleration-limit") { + setRotationAccelerationLimit(RIGHT_FOOT_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "hips-translation-acceleration-limit") { + setTranslationAccelerationLimit(HIPS_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "hips-rotation-acceleration-limit") { + setRotationAccelerationLimit(HIPS_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "spine2-translation-acceleration-limit") { + setTranslationAccelerationLimit(SPINE2_INDEX, parseInt(msg.val, 10)); + } else if (msg.name === "spine2-rotation-acceleration-limit") { + setRotationAccelerationLimit(SPINE2_INDEX, parseInt(msg.val, 10)); } } From a776f7e55a98bd09c729a2ca888d6b33b0d5ada6 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 25 Sep 2018 10:24:30 -0700 Subject: [PATCH 066/276] Changed default for OutOfRange data to Drop. --- plugins/openvr/src/ViveControllerManager.cpp | 2 +- plugins/openvr/src/ViveControllerManager.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index cc3ca523df..69797340dd 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -310,7 +310,7 @@ void ViveControllerManager::loadSettings() { if (_inputDevice) { const double DEFAULT_ARM_CIRCUMFERENCE = 0.33; const double DEFAULT_SHOULDER_WIDTH = 0.48; - const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "None"; + const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "Drop"; _inputDevice->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble(); _inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble(); _inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString()); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index f59ed9d62a..06e13e1c49 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -169,7 +169,7 @@ private: FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; std::string _headsetName {""}; - OutOfRangeDataStrategy _outOfRangeDataStrategy { OutOfRangeDataStrategy::None }; + OutOfRangeDataStrategy _outOfRangeDataStrategy { OutOfRangeDataStrategy::Drop }; std::vector _validTrackedObjects; std::map _pucksPostOffset; From 0283c9fbdc34fcee1b81232f215afee02c146e17 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 2 Oct 2018 16:20:00 -0700 Subject: [PATCH 067/276] Added CriticallyDampedSpringPoseHelper to help smooth out hips. --- interface/src/avatar/MySkeletonModel.cpp | 43 ++++++++----------- interface/src/avatar/MySkeletonModel.h | 6 ++- libraries/animation/src/AnimUtil.h | 53 ++++++++++++++++++++++++ libraries/animation/src/Rig.cpp | 3 ++ libraries/animation/src/Rig.h | 2 + 5 files changed, 79 insertions(+), 28 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 3084542472..f13a4c8e10 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -36,6 +36,7 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); + // check for pinned hips. auto hipsIndex = myAvatar->getJointIndex("Hips"); if (myAvatar->isJointPinned(hipsIndex)) { @@ -199,49 +200,38 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); - if (!_prevHipsValid) { - AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying); - _prevHips = hips; - } - - AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying); - // timescale in seconds const float TRANS_HORIZ_TIMESCALE = 0.15f; const float TRANS_VERT_TIMESCALE = 0.01f; // We want the vertical component of the hips to follow quickly to prevent spine squash/stretch. const float ROT_TIMESCALE = 0.15f; const float FLY_IDLE_TRANSITION_TIMESCALE = 0.25f; - float transHorizAlpha, transVertAlpha, rotAlpha; if (_flyIdleTimer < 0.0f) { - transHorizAlpha = glm::min(deltaTime / TRANS_HORIZ_TIMESCALE, 1.0f); - transVertAlpha = glm::min(deltaTime / TRANS_VERT_TIMESCALE, 1.0f); - rotAlpha = glm::min(deltaTime / ROT_TIMESCALE, 1.0f); + _smoothHipsHelper.setHorizontalTranslationTimescale(TRANS_HORIZ_TIMESCALE); + _smoothHipsHelper.setVerticalTranslationTimescale(TRANS_VERT_TIMESCALE); + _smoothHipsHelper.setRotationTimescale(ROT_TIMESCALE); } else { - transHorizAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); - transVertAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); - rotAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f); + _smoothHipsHelper.setHorizontalTranslationTimescale(FLY_IDLE_TRANSITION_TIMESCALE); + _smoothHipsHelper.setVerticalTranslationTimescale(FLY_IDLE_TRANSITION_TIMESCALE); + _smoothHipsHelper.setRotationTimescale(FLY_IDLE_TRANSITION_TIMESCALE); } - // smootly lerp hips, in sensorframe, with different coeff for horiz and vertical translation. - float hipsY = hips.trans().y; - hips.trans() = lerp(_prevHips.trans(), hips.trans(), transHorizAlpha); - hips.trans().y = lerp(_prevHips.trans().y, hipsY, transVertAlpha); - hips.rot() = safeLerp(_prevHips.rot(), hips.rot(), rotAlpha); - - _prevHips = hips; - _prevHipsValid = true; + AnimPose sensorHips = computeHipsInSensorFrame(myAvatar, isFlying); + if (!_prevIsEstimatingHips) { + _smoothHipsHelper.teleport(sensorHips); + } + sensorHips = _smoothHipsHelper.update(sensorHips, deltaTime); glm::mat4 invRigMat = glm::inverse(myAvatar->getTransform().getMatrix() * Matrices::Y_180); AnimPose sensorToRigPose(invRigMat * myAvatar->getSensorToWorldMatrix()); - params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * hips; + params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * sensorHips; params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; // set spine2 if we have hand controllers if (myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && - myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && - !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { + myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && + !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { AnimPose currentSpine2Pose; AnimPose currentHeadPose; @@ -267,8 +257,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } + _prevIsEstimatingHips = true; } else { - _prevHipsValid = false; + _prevIsEstimatingHips = false; } params.isTalking = head->getTimeWithoutTalking() <= 1.5f; diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index ebef9796a4..9a3559ddf7 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -10,6 +10,7 @@ #define hifi_MySkeletonModel_h #include +#include #include "MyAvatar.h" /// A skeleton loaded from a model. @@ -26,11 +27,12 @@ public: private: void updateFingers(); - AnimPose _prevHips; // sensor frame - bool _prevHipsValid { false }; + CriticallyDampedSpringPoseHelper _smoothHipsHelper; // sensor frame bool _prevIsFlying { false }; float _flyIdleTimer { 0.0f }; + float _prevIsEstimatingHips { false }; + std::map _jointRotationFrameOffsetMap; }; diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 9300f1a7a0..1880550435 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -38,4 +38,57 @@ AnimPose boneLookAt(const glm::vec3& target, const AnimPose& bone); // and returns a bodyRot that is also z-forward and y-up glm::quat computeBodyFacingFromHead(const glm::quat& headRot, const glm::vec3& up); + +// Uses a approximation of a critically damped spring to smooth full AnimPoses. +// It provides seperate timescales for horizontal, vertical and rotation components. +// The timescale is roughly how much time it will take the spring will reach halfway toward it's target. +class CriticallyDampedSpringPoseHelper { +public: + CriticallyDampedSpringPoseHelper() : _prevPoseValid(false) {} + + void setHorizontalTranslationTimescale(float timescale) { + _horizontalTranslationTimescale = timescale; + } + void setVerticalTranslationTimescale(float timescale) { + _verticalTranslationTimescale = timescale; + } + void setRotationTimescale(float timescale) { + _rotationTimescale = timescale; + } + + AnimPose update(const AnimPose& pose, float deltaTime) { + if (!_prevPoseValid) { + _prevPose = pose; + _prevPoseValid = true; + } + + const float horizontalTranslationAlpha = glm::min(deltaTime / _horizontalTranslationTimescale, 1.0f); + const float verticalTranslationAlpha = glm::min(deltaTime / _verticalTranslationTimescale, 1.0f); + const float rotationAlpha = glm::min(deltaTime / _rotationTimescale, 1.0f); + + const float poseY = pose.trans().y; + AnimPose newPose = _prevPose; + newPose.trans() = lerp(_prevPose.trans(), pose.trans(), horizontalTranslationAlpha); + newPose.trans().y = lerp(_prevPose.trans().y, poseY, verticalTranslationAlpha); + newPose.rot() = safeLerp(_prevPose.rot(), pose.rot(), rotationAlpha); + + _prevPose = newPose; + _prevPoseValid = true; + + return newPose; + } + + void teleport(const AnimPose& pose) { + _prevPoseValid = true; + _prevPose = pose; + } + +protected: + AnimPose _prevPose; + float _horizontalTranslationTimescale { 0.15f }; + float _verticalTranslationTimescale { 0.15f }; + float _rotationTimescale { 0.15f }; + bool _prevPoseValid; +}; + #endif diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 91d4e0f9d3..a1f3fbe8c8 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1609,6 +1609,7 @@ glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int up void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) { if (!_animSkeleton || !_animNode) { + _previousControllerParameters = params; return; } @@ -1700,6 +1701,8 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } } } + + _previousControllerParameters = params; } void Rig::initAnimGraph(const QUrl& url) { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 48f00d4e5d..4b1c994605 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -394,6 +394,8 @@ protected: AnimContext _lastContext; AnimVariantMap _lastAnimVars; + + ControllerParameters _previousControllerParameters; }; #endif /* defined(__hifi__Rig__) */ From 4e751237401e076b374950b00b34a9b4a7171311 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 2 Oct 2018 18:07:26 -0700 Subject: [PATCH 068/276] Blend between non-estimated and estimated hips in Rig --- libraries/animation/src/AnimUtil.h | 37 ++++++++++++++++++++++++++++++ libraries/animation/src/Rig.cpp | 23 +++++++++++++++++-- libraries/animation/src/Rig.h | 2 ++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 1880550435..cf190e8dbf 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -91,4 +91,41 @@ protected: bool _prevPoseValid; }; +class SnapshotBlendPoseHelper { +public: + SnapshotBlendPoseHelper() : _snapshotValid(false) {} + + void setBlendDuration(float duration) { + _duration = duration; + } + + void setSnapshot(const AnimPose& pose) { + _snapshotValid = true; + _snapshotPose = pose; + _timer = _duration; + } + + AnimPose update(const AnimPose& targetPose, float deltaTime) { + _timer -= deltaTime; + if (_timer > 0.0f) { + float alpha = (_duration - _timer) / _duration; + + // ease in expo + alpha = 1.0f - powf(2.0f, -10.0f * alpha); + + AnimPose newPose = targetPose; + newPose.blend(_snapshotPose, alpha); + return newPose; + } else { + return targetPose; + } + } + +protected: + AnimPose _snapshotPose; + float _duration { 1.0f }; + float _timer { 0.0f }; + bool _snapshotValid { false }; +}; + #endif diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index a1f3fbe8c8..d076ce5029 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1620,7 +1620,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled; bool rightHandEnabled = params.primaryControllerFlags[PrimaryControllerType_RightHand] & (uint8_t)ControllerFlags::Enabled; bool hipsEnabled = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; + bool prevHipsEnabled = _previousControllerParameters.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; bool hipsEstimated = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated; + bool prevHipsEstimated = _previousControllerParameters.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated; bool leftFootEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftFoot] & (uint8_t)ControllerFlags::Enabled; bool rightFootEnabled = params.primaryControllerFlags[PrimaryControllerType_RightFoot] & (uint8_t)ControllerFlags::Enabled; bool spine2Enabled = params.primaryControllerFlags[PrimaryControllerType_Spine2] & (uint8_t)ControllerFlags::Enabled; @@ -1659,9 +1661,26 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } if (hipsEnabled) { + + // Apply a bit of smoothing when the hips toggle between estimated and non-estimated poses. + // This should help smooth out problems with the vive tracker when the sensor is occluded. + if (prevHipsEnabled && hipsEstimated != prevHipsEstimated) { + // blend from a snapshot of the previous hips. + const float HIPS_BLEND_DURATION = 0.3f; + _hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION); + _hipsBlendHelper.setSnapshot(_previousControllerParameters.primaryControllerPoses[PrimaryControllerType_Hips]); + } else if (!prevHipsEnabled) { + // we have no sensible value to blend from. + const float HIPS_BLEND_DURATION = 0.0f; + _hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION); + _hipsBlendHelper.setSnapshot(params.primaryControllerPoses[PrimaryControllerType_Hips]); + } + + AnimPose hips = _hipsBlendHelper.update(params.primaryControllerPoses[PrimaryControllerType_Hips], dt); + _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); - _animVars.set("hipsPosition", params.primaryControllerPoses[PrimaryControllerType_Hips].trans()); - _animVars.set("hipsRotation", params.primaryControllerPoses[PrimaryControllerType_Hips].rot()); + _animVars.set("hipsPosition", hips.trans()); + _animVars.set("hipsRotation", hips.rot()); } else { _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 4b1c994605..3a2fa2d036 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -24,6 +24,7 @@ #include "AnimNode.h" #include "AnimNodeLoader.h" #include "SimpleMovingAverage.h" +#include "AnimUtil.h" class Rig; class AnimInverseKinematics; @@ -395,6 +396,7 @@ protected: AnimContext _lastContext; AnimVariantMap _lastAnimVars; + SnapshotBlendPoseHelper _hipsBlendHelper; ControllerParameters _previousControllerParameters; }; From 60d102c9bfa7ffa542f827d6869f7ae76a5c524c Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 9 Oct 2018 12:42:57 -0700 Subject: [PATCH 069/276] styling fixes --- scripts/system/html/css/edit-style.css | 39 ++---- scripts/system/html/js/entityProperties.js | 151 ++++++++++++++++++--- 2 files changed, 142 insertions(+), 48 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index e700b50839..f69ace2401 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -478,13 +478,11 @@ input[type=checkbox]:checked + label:hover { box-shadow: none; } -#properties-list > fieldset#properties-header { +#properties-list > fieldset#properties-base { margin-top: 0px; padding-bottom: 0px; } - - #properties-list > fieldset > legend { position: relative; display: table; @@ -896,7 +894,7 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { } #properties-list fieldset .two-column { - padding-top:21px; + padding-top: 21px; display: flex; } @@ -917,7 +915,7 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { font-family: Raleway-Regular; font-size: 12px; color: #afafaf; - height: 28px; + height: 20px; text-transform: uppercase; outline: none; } @@ -1270,7 +1268,7 @@ th#entity-hasScript { color: #afafaf; } -#base #property-type-icon { +#properties-base #property-type-icon { font-family: hifi-glyphs; font-size: 31px; color: #00b4ef; @@ -1279,52 +1277,39 @@ th#entity-hasScript { display: none; } -#base #property-type { +#properties-base #property-type { padding: 5px 24px 5px 0; border-right: 1px solid #808080; width: auto; display: inline-block; } -#base #div-locked { +#properties-base #div-property-locked { position: absolute; top: 0px; right: 140px; } -#base #div-visible { +#properties-base #div-property-visible { position: absolute; top: 20px; right: 20px; } -#base .checkbox { - position: relative; - top: -1px; -} - -#base .checkbox:last-child { - padding-left: 24px; -} - -#base .checkbox label { - background-position-y: 1px; -} - -#base .checkbox label span { +#properties-base .checkbox label span { font-family: HiFi-Glyphs; font-size: 20px; padding-right: 6px; vertical-align: top; position: relative; - top: -4px; + top: -2px; } -#base input[type=checkbox]:checked + label span { +#properties-base input[type=checkbox]:checked + label span { color: #ffffff; } -#base + hr { +#properties-base + hr { margin-top: 12px; } @@ -1387,4 +1372,4 @@ input#property-scale-button-reset { #properties-list { display: flex; flex-direction: column; -} \ No newline at end of file +} diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d8ffc2ee25..46f16dd0a0 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -775,7 +775,7 @@ const GROUPS = [ { label: "Can cast shadow", type: "bool", - propertyName: "castShadow", + propertyName: "canCastShadow", }, { label: "Script", @@ -957,12 +957,6 @@ function disableProperties() { } } -function showElements(els, show) { - for (var i = 0; i < els.length; i++) { - els[i].style.display = (show) ? 'table' : 'none'; - } -} - function updateProperty(propertyName, propertyValue) { var properties = {}; let splitPropertyName = propertyName.split('.'); @@ -1688,11 +1682,17 @@ function resetProperties() { for (let propertyToHide in propertyShowRules) { let elPropertyToHide = elPropertyElements[propertyToHide]; if (elPropertyToHide) { - let parentNode = elPropertyToHide.parentNode; - if (parentNode === undefined && elPropertyToHide instanceof Array) { - parentNode = elPropertyToHide[0].parentNode; + let nodeToHide = elPropertyToHide; + if (elPropertyToHide.nodeName !== "DIV") { + let parentNode = elPropertyToHide.parentNode; + if (parentNode === undefined && elPropertyToHide instanceof Array) { + parentNode = elPropertyToHide[0].parentNode; + } + if (parentNode !== undefined) { + nodeToHide = parentNode; + } } - parentNode.style.display = "none"; + nodeToHide.style.display = "none"; } } } @@ -1705,13 +1705,13 @@ function loaded() { GROUPS.forEach(function(group) { let elGroup; if (group.addToGroup !== undefined) { - let fieldset = document.getElementById(group.addToGroup); + let fieldset = document.getElementById("properties-" + group.addToGroup); elGroup = document.createElement('div'); fieldset.appendChild(elGroup); } else { elGroup = document.createElement('fieldset'); elGroup.setAttribute("class", "major"); - elGroup.setAttribute("id", group.id); + elGroup.setAttribute("id", "properties-" + group.id); elPropertiesList.appendChild(elGroup); } @@ -1739,7 +1739,7 @@ function loaded() { elProperty.setAttribute("class", "sub-section-header"); } else { elProperty = document.createElement('div'); - elProperty.setAttribute("id", "div-" + propertyName); + elProperty.setAttribute("id", "div-" + propertyID); } if (group.twoColumn && property.column !== undefined && property.column !== -1) { @@ -1944,6 +1944,7 @@ function loaded() { let elInput = document.createElement('select'); elInput.setAttribute("id", propertyID); + elInput.setAttribute("propertyName", propertyName); for (let optionKey in property.options) { let option = document.createElement('option'); @@ -2035,7 +2036,7 @@ function loaded() { break; } case 'buttons': { - elProperty.setAttribute("class", "property Text"); + elProperty.setAttribute("class", "property text"); let hasLabel = property.label !== undefined; if (hasLabel) { @@ -2234,7 +2235,7 @@ function loaded() { elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); if (elProperty[2] !== undefined) { - elProperty[2].value = (propertyValue.z * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); + elProperty[2].value = (propertyValue.z * 1 / elProperty[2].getAttribute("multiplier")).toFixed(4); } } else if (elProperty.length === 4) { // color is array of color picker and 3 input numbers @@ -2262,8 +2263,9 @@ function loaded() { } else if (elProperty.nodeName === "TEXTAREA") { elProperty.value = propertyValue; setTextareaScrolling(elProperty); - } else if (elProperty.nodeName === "SELECT") { // dropdown + } else if (elProperty.nodeName === "DT") { // dropdown elProperty.value = propertyValue; + setDropdownText(elProperty); } else { elProperty.value = propertyValue; } @@ -2275,11 +2277,17 @@ function loaded() { let show = String(propertyValue) === String(showIfThisPropertyValue); let elPropertyToShow = elPropertyElements[propertyToShow]; if (elPropertyToShow) { - let parentNode = elPropertyToShow.parentNode; - if (parentNode === undefined && elPropertyToShow instanceof Array) { - parentNode = elPropertyToShow[0].parentNode; + let nodeToShow = elPropertyToShow; + if (elPropertyToShow.nodeName !== "DIV") { + let parentNode = elPropertyToShow.parentNode; + if (parentNode === undefined && elPropertyToShow instanceof Array) { + parentNode = elPropertyToShow[0].parentNode; + } + if (parentNode !== undefined) { + nodeToShow = parentNode; + } } - parentNode.style.display = show ? "block" : "none"; + nodeToShow.style.display = show ? "table" : "none"; } } } @@ -2524,6 +2532,107 @@ function loaded() { event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); } + + // Dropdowns + // For each dropdown the following replacement is created in place of the original dropdown... + // Structure created: + //
    + //
    display textcarat
    + //
    + //
      + //
    • 0) { + var el = elDropdowns[0]; + el.parentNode.removeChild(el); + elDropdowns = document.getElementsByTagName("select"); + } document.addEventListener("keydown", function (keyDown) { if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { From 8d56a313e891664af3ba118dbc573eda5a05c811 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 9 Oct 2018 14:05:26 -0700 Subject: [PATCH 070/276] renamed viveTrackedObjects.js to drawTrackedObjects.js Removed requirement of having a Vive hooked up. Input recordings can have trackedObjects within them. --- ...TrackedObjects.js => drawTrackedObjects.js} | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) rename scripts/developer/tests/{viveTrackedObjects.js => drawTrackedObjects.js} (62%) diff --git a/scripts/developer/tests/viveTrackedObjects.js b/scripts/developer/tests/drawTrackedObjects.js similarity index 62% rename from scripts/developer/tests/viveTrackedObjects.js rename to scripts/developer/tests/drawTrackedObjects.js index 1d60f658d9..c7d886c319 100644 --- a/scripts/developer/tests/viveTrackedObjects.js +++ b/scripts/developer/tests/drawTrackedObjects.js @@ -21,16 +21,14 @@ function shutdown() { var BLUE = {x: 0, y: 0, z: 1, w: 1}; function update(dt) { - if (Controller.Hardware.Vive) { - TRACKED_OBJECT_POSES.forEach(function (key) { - var pose = Controller.getPoseValue(Controller.Standard[key]); - if (pose.valid) { - DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE); - } else { - DebugDraw.removeMyAvatarMarker(key); - } - }); - } + TRACKED_OBJECT_POSES.forEach(function (key) { + var pose = Controller.getPoseValue(Controller.Standard[key]); + if (pose.valid) { + DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE); + } else { + DebugDraw.removeMyAvatarMarker(key); + } + }); } init(); From c0ceb713ec6315f24feb5bf52cd92c2cb522af3f Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Tue, 9 Oct 2018 16:48:28 -0700 Subject: [PATCH 071/276] Spine2 IKTarget now smoothly interpolates when ik target is disabled. Also all smooth ik interpolations use easeInExpo instead of linear curves. --- .../animation/src/AnimInverseKinematics.cpp | 20 ++++++++++++++++--- libraries/animation/src/AnimTwoBoneIK.cpp | 7 +++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 71094cc6e1..399eaf3fab 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -253,11 +253,25 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector< if (numLoops == MAX_IK_LOOPS) { for (size_t i = 0; i < _prevJointChainInfoVec.size(); i++) { if (_prevJointChainInfoVec[i].timer > 0.0f) { + float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[i].timer) / JOINT_CHAIN_INTERP_TIME; + + // ease in expo + alpha = 1.0f - powf(2.0f, -10.0f * alpha); + size_t chainSize = std::min(_prevJointChainInfoVec[i].jointInfoVec.size(), jointChainInfoVec[i].jointInfoVec.size()); - for (size_t j = 0; j < chainSize; j++) { - jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha); - jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha); + + if (jointChainInfoVec[i].target.getType() != IKTarget::Type::Unknown) { + // if we are interping into an enabled target type, i.e. not off, lerp the rot and the trans. + for (size_t j = 0; j < chainSize; j++) { + jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha); + jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha); + } + } else { + // if we are interping into a disabled target type, keep the rot & trans the same, but lerp the weight down to zero. + jointChainInfoVec[i].target.setType((int)_prevJointChainInfoVec[i].target.getType()); + jointChainInfoVec[i].target.setWeight(_prevJointChainInfoVec[i].target.getWeight() * (1.0f - alpha)); + jointChainInfoVec[i].jointInfoVec = _prevJointChainInfoVec[i].jointInfoVec; } } } diff --git a/libraries/animation/src/AnimTwoBoneIK.cpp b/libraries/animation/src/AnimTwoBoneIK.cpp index d68240d176..8960b15940 100644 --- a/libraries/animation/src/AnimTwoBoneIK.cpp +++ b/libraries/animation/src/AnimTwoBoneIK.cpp @@ -201,14 +201,17 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const if (_interpType != InterpType::None) { _interpAlpha += _interpAlphaVel * dt; + // ease in expo + float easeInAlpha = 1.0f - powf(2.0f, -10.0f * _interpAlpha); + if (_interpAlpha < 1.0f) { AnimChain interpChain; if (_interpType == InterpType::SnapshotToUnderPoses) { interpChain = underChain; - interpChain.blend(_snapshotChain, _interpAlpha); + interpChain.blend(_snapshotChain, easeInAlpha); } else if (_interpType == InterpType::SnapshotToSolve) { interpChain = ikChain; - interpChain.blend(_snapshotChain, _interpAlpha); + interpChain.blend(_snapshotChain, easeInAlpha); } // copy interpChain into _poses interpChain.outputRelativePoses(_poses); From 296ea8a5af821e5f8bfeaac3e85c6eb002813189 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 10 Oct 2018 12:00:58 -0700 Subject: [PATCH 072/276] fix overlay issues during interstitial mode --- .../controllerModules/webSurfaceLaserInput.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 2412e2fa1c..cf61693dfd 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -20,6 +20,7 @@ Script.include("/~/system/libraries/controllers.js"); this.hand = hand; this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND; this.running = false; + this.ignoredOverlays = []; this.parameters = makeDispatcherModuleParameters( 160, @@ -67,6 +68,42 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; + this.addOverlayToIgnoreList = function(controllerData) { + if (Window.interstitialModeEnabled && !Window.isPhysicsEnabled()) { + var intersection = controllerData.rayPicks[this.hand]; + var objectID = intersection.objectID; + + if (intersection.type === Picks.INTERSECTED_OVERLAY) { + var overlayIndex = this.ignoredOverlays.indexOf(objectID); + + if (overlayIndex === -1) { + var overlayName = Overlays.getProperty(objectID, "name"); + if (overlayName !== "Loading-Destination-Card-Text" && overlayName !== "Loading-Destination-Card-GoTo-Image" && + overlayName !== "Loading-Destination-Card-GoTo-Image-Hover") { + var data = { + action: 'add', + id: objectID + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); + this.ignoredOverlays.push(objectID); + } + } + } + } + }; + + this.restoreIgnoredOverlays = function() { + for (var index = 0; index < this.ignoredOverlays.length; index++) { + var data = { + action: 'remove', + id: this.ignoredOverlays[index] + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); + } + + this.ignoredOverlays = []; + }; + this.isPointingAtTriggerable = function(controllerData, triggerPressed, checkEntitiesOnly) { // 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 @@ -143,6 +180,7 @@ 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; + this.addOverlayToIgnoreList(controllerData); if (allowThisModule) { if (isTriggerPressed && !this.isPointingAtTriggerable(controllerData, isTriggerPressed, true)) { // if trigger is down + not pointing at a web entity, keep running web surface laser @@ -156,6 +194,7 @@ Script.include("/~/system/libraries/controllers.js"); this.deleteContextOverlay(); this.running = false; this.dominantHandOverride = false; + this.restoreIgnoredOverlays(); return makeRunningValues(false, [], []); } } @@ -163,6 +202,7 @@ Script.include("/~/system/libraries/controllers.js"); this.deleteContextOverlay(); this.running = false; this.dominantHandOverride = false; + this.restoreIgnoredOverlays(); return makeRunningValues(false, [], []); }; } From 82918a5c31c64767fbbcf3b77332564e193b4134 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 10 Oct 2018 12:31:16 -0700 Subject: [PATCH 073/276] Add animation support --- interface/src/avatar/AvatarManager.cpp | 41 +++++++++++++++++++ interface/src/avatar/AvatarManager.h | 1 + .../src/avatars-renderer/Avatar.h | 14 +++++++ 3 files changed, 56 insertions(+) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d31b201dc7..1f2c9e462d 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -83,10 +83,18 @@ AvatarManager::AvatarManager(QObject* parent) : const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing const int AVATAR_TRANSIT_FRAMES_PER_METER = 1; // Based on testing + const QString START_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; + const QString MIDDLE_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; + const QString END_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; + _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; _transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE; _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; _transitConfig._isDistanceBased = true; + + _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 14); + _transitConfig._middleTransitAnimation = AvatarTransit::TransitAnimation(MIDDLE_ANIMATION_URL, 15, 0); + _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 16, 38); } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { @@ -134,6 +142,39 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) { _space = space; } +void AvatarManager::playTransitAnimations(AvatarTransit::Status status) { + auto startAnimation = _transitConfig._startTransitAnimation; + auto middleAnimation = _transitConfig._middleTransitAnimation; + auto endAnimation = _transitConfig._endTransitAnimation; + + const float REFERENCE_FPS = 30.0f; + switch (status) { + case AvatarTransit::Status::START_FRAME: + qDebug() << "START_FRAME"; + _myAvatar->overrideAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, + startAnimation._firstFrame + startAnimation._frameCount); + break; + case AvatarTransit::Status::START_TRANSIT: + qDebug() << "START_TRANSIT"; + _myAvatar->overrideAnimation(middleAnimation._animationUrl, REFERENCE_FPS, false, middleAnimation._firstFrame, + middleAnimation._firstFrame + middleAnimation._frameCount); + break; + case AvatarTransit::Status::END_TRANSIT: + qDebug() << "END_TRANSIT"; + _myAvatar->overrideAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, + endAnimation._firstFrame + endAnimation._frameCount); + break; + case AvatarTransit::Status::END_FRAME: + qDebug() << "END_FRAME"; + _myAvatar->restoreAnimation(); + break; + case AvatarTransit::Status::IDLE: + break; + case AvatarTransit::Status::TRANSITING: + break; + } +} + void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 209b976c44..0dc39e991b 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -213,6 +213,7 @@ private: // frequently grabs a read lock on the hash to get a given avatar by ID void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; + void playTransitAnimations(AvatarTransit::Status status); QVector _avatarsToFade; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 1087f74c07..21e359051f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -54,9 +54,11 @@ class AvatarTransit { public: enum Status { IDLE = 0, + START_FRAME, START_TRANSIT, TRANSITING, END_TRANSIT, + END_FRAME, ABORT_TRANSIT }; @@ -67,6 +69,15 @@ public: EASE_IN_OUT }; + struct TransitAnimation { + TransitAnimation(){}; + TransitAnimation(const QString& animationUrl, int firstFrame, int frameCount) : + _firstFrame(firstFrame), _frameCount(frameCount), _animationUrl(animationUrl){}; + int _firstFrame; + int _frameCount; + QString _animationUrl; + }; + struct TransitConfig { TransitConfig() {}; int _totalFrames { 0 }; @@ -74,6 +85,9 @@ public: bool _isDistanceBased { false }; float _triggerDistance { 0 }; EaseType _easeType { EaseType::EASE_OUT }; + TransitAnimation _startTransitAnimation; + TransitAnimation _middleTransitAnimation; + TransitAnimation _endTransitAnimation; }; AvatarTransit() {}; From 09c3b39f0d14202a6efb17425a68e4ea46efd0c1 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 10 Oct 2018 14:00:16 -0700 Subject: [PATCH 074/276] Update puck-attach.js with model that matches the orientation of the pucks --- scripts/developer/tests/puck-attach.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/developer/tests/puck-attach.js b/scripts/developer/tests/puck-attach.js index 04d5db5710..019a911535 100644 --- a/scripts/developer/tests/puck-attach.js +++ b/scripts/developer/tests/puck-attach.js @@ -85,7 +85,7 @@ function entityExists(entityID) { return Object.keys(Entities.getEntityProperties(entityID)).length > 0; } -var VIVE_PUCK_MODEL = "http://content.highfidelity.com/seefo/production/puck-attach/vive_tracker_puck.obj"; +var VIVE_PUCK_MODEL = "https://s3.amazonaws.com/hifi-public/tony/vive_tracker_puck_y180z180.obj"; var VIVE_PUCK_DIMENSIONS = { x: 0.0945, y: 0.0921, z: 0.0423 }; // 1/1000th scale of model var VIVE_PUCK_SEARCH_DISTANCE = 1.5; // metres var VIVE_PUCK_SPAWN_DISTANCE = 0.5; // metres @@ -304,4 +304,4 @@ tabletButton.clicked.connect(function () { tablet.gotoWebScreen(TABLET_APP_URL); } }); -}()); // END LOCAL_SCOPE \ No newline at end of file +}()); // END LOCAL_SCOPE From a47a1c03e6b68492003aac9c3aa237bbc40dc6c3 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 10 Oct 2018 14:08:47 -0700 Subject: [PATCH 075/276] ignore web entities --- .../controllerModules/webSurfaceLaserInput.js | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index cf61693dfd..898164dc99 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -20,7 +20,7 @@ Script.include("/~/system/libraries/controllers.js"); this.hand = hand; this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND; this.running = false; - this.ignoredOverlays = []; + this.ignoredObjects = []; this.parameters = makeDispatcherModuleParameters( 160, @@ -68,35 +68,43 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; - this.addOverlayToIgnoreList = function(controllerData) { + this.addObjectToIgnoreList = function(controllerData) { if (Window.interstitialModeEnabled && !Window.isPhysicsEnabled()) { var intersection = controllerData.rayPicks[this.hand]; var objectID = intersection.objectID; if (intersection.type === Picks.INTERSECTED_OVERLAY) { - var overlayIndex = this.ignoredOverlays.indexOf(objectID); + var overlayIndex = this.ignoredObjects.indexOf(objectID); - if (overlayIndex === -1) { - var overlayName = Overlays.getProperty(objectID, "name"); - if (overlayName !== "Loading-Destination-Card-Text" && overlayName !== "Loading-Destination-Card-GoTo-Image" && - overlayName !== "Loading-Destination-Card-GoTo-Image-Hover") { - var data = { - action: 'add', - id: objectID - }; - Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); - this.ignoredOverlays.push(objectID); - } + var overlayName = Overlays.getProperty(objectID, "name"); + if (overlayName !== "Loading-Destination-Card-Text" && overlayName !== "Loading-Destination-Card-GoTo-Image" && + overlayName !== "Loading-Destination-Card-GoTo-Image-Hover") { + var data = { + action: 'add', + id: objectID + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); + this.ignoredObjects.push(objectID); } + } else if (intersection.type === Picks.INTERSECTED_ENTITY) { + var entityIndex = this.ignoredObjects.indexOf(objectID); + var data = { + action: 'add', + id: objectID + }; + print("ignoreing entity " + entityIndex); + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); + this.ignoredObjects.push(objectID); } } }; - this.restoreIgnoredOverlays = function() { - for (var index = 0; index < this.ignoredOverlays.length; index++) { + this.restoreIgnoredObjects = function() { + for (var index = 0; index < this.ignoredObjects.length; index++) { + print("removing"); var data = { action: 'remove', - id: this.ignoredOverlays[index] + id: this.ignoredObjects[index] }; Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); } @@ -168,6 +176,10 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(true, [], []); } } + + if (Window.interstitialModeEnabled && Window.isPhysicsEnabled()) { + this.restoreIgnoredObjects(); + } return makeRunningValues(false, [], []); }; @@ -180,7 +192,7 @@ 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; - this.addOverlayToIgnoreList(controllerData); + this.addObjectToIgnoreList(controllerData); if (allowThisModule) { if (isTriggerPressed && !this.isPointingAtTriggerable(controllerData, isTriggerPressed, true)) { // if trigger is down + not pointing at a web entity, keep running web surface laser @@ -194,7 +206,6 @@ Script.include("/~/system/libraries/controllers.js"); this.deleteContextOverlay(); this.running = false; this.dominantHandOverride = false; - this.restoreIgnoredOverlays(); return makeRunningValues(false, [], []); } } @@ -202,7 +213,6 @@ Script.include("/~/system/libraries/controllers.js"); this.deleteContextOverlay(); this.running = false; this.dominantHandOverride = false; - this.restoreIgnoredOverlays(); return makeRunningValues(false, [], []); }; } From be8f4256c3478094805b8142a4461a8a0ceb1ce3 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 Sep 2017 16:45:39 -0700 Subject: [PATCH 076/276] Add ccache support for Linux and Mac --- cmake/compiler.cmake | 4 +++ cmake/macros/ConfigureCCache.cmake | 45 ++++++++++++++++++++++++++++++ cmake/templates/launch-c.in | 12 ++++++++ cmake/templates/launch-cxx.in | 12 ++++++++ 4 files changed, 73 insertions(+) create mode 100644 cmake/macros/ConfigureCCache.cmake create mode 100644 cmake/templates/launch-c.in create mode 100644 cmake/templates/launch-cxx.in diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake index 6ff6fce1d8..927871773d 100644 --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -6,6 +6,10 @@ if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8") message( FATAL_ERROR "Only 64 bit builds supported." ) endif() +if (USE_CCACHE OR "$ENV{USE_CCACHE}") + configure_ccache() +endif() + if (WIN32) add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) diff --git a/cmake/macros/ConfigureCCache.cmake b/cmake/macros/ConfigureCCache.cmake new file mode 100644 index 0000000000..6107faaa21 --- /dev/null +++ b/cmake/macros/ConfigureCCache.cmake @@ -0,0 +1,45 @@ +# +# ConfigureCCache.cmake +# cmake/macros +# +# Created by Clement Brisset on 10/10/18. +# 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 +# + +macro(configure_ccache) + find_program(CCACHE_PROGRAM ccache) + if(CCACHE_PROGRAM) + message(STATUS "Configuring ccache") + + # Set up wrapper scripts + set(C_LAUNCHER "${CCACHE_PROGRAM}") + set(CXX_LAUNCHER "${CCACHE_PROGRAM}") + + set(LAUNCH_C_IN "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/launch-c.in") + set(LAUNCH_CXX_IN "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/launch-cxx.in") + set(LAUNCH_C "${CMAKE_BINARY_DIR}/CMakeFiles/launch-c") + set(LAUNCH_CXX "${CMAKE_BINARY_DIR}/CMakeFiles/launch-cxx") + + configure_file(${LAUNCH_C_IN} ${LAUNCH_C}) + configure_file(${LAUNCH_CXX_IN} ${LAUNCH_CXX}) + execute_process(COMMAND chmod a+rx ${LAUNCH_C} ${LAUNCH_CXX}) + + if(CMAKE_GENERATOR STREQUAL "Xcode") + # Set Xcode project attributes to route compilation and linking + # through our scripts + set(CMAKE_XCODE_ATTRIBUTE_CC ${LAUNCH_C}) + set(CMAKE_XCODE_ATTRIBUTE_CXX ${LAUNCH_CXX}) + set(CMAKE_XCODE_ATTRIBUTE_LD ${LAUNCH_C}) + set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${LAUNCH_CXX}) + else() + # Support Unix Makefiles and Ninja + set(CMAKE_C_COMPILER_LAUNCHER ${LAUNCH_C}) + set(CMAKE_CXX_COMPILER_LAUNCHER ${LAUNCH_CXX}) + endif() + else() + message(WARNING "Could not find ccache") + endif() +endmacro() diff --git a/cmake/templates/launch-c.in b/cmake/templates/launch-c.in new file mode 100644 index 0000000000..6c91d96dd9 --- /dev/null +++ b/cmake/templates/launch-c.in @@ -0,0 +1,12 @@ +#!/bin/sh + +# Xcode generator doesn't include the compiler as the +# first argument, Ninja and Makefiles do. Handle both cases. +if [[ "$1" = "${CMAKE_C_COMPILER}" ]] ; then + shift +fi + +export CCACHE_CPP2=true +export CCACHE_HARDLINK=true +export CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches +exec "${C_LAUNCHER}" "${CMAKE_C_COMPILER}" "$@" diff --git a/cmake/templates/launch-cxx.in b/cmake/templates/launch-cxx.in new file mode 100644 index 0000000000..4215d89c80 --- /dev/null +++ b/cmake/templates/launch-cxx.in @@ -0,0 +1,12 @@ +#!/bin/sh + +# Xcode generator doesn't include the compiler as the +# first argument, Ninja and Makefiles do. Handle both cases. +if [[ "$1" = "${CMAKE_CXX_COMPILER}" ]] ; then + shift +fi + +export CCACHE_CPP2=true +export CCACHE_HARDLINK=true +export CCACHE_SLOPPINESS=file_macro,time_macros,include_file_mtime,include_file_ctime,file_stat_matches +exec "${CXX_LAUNCHER}" "${CMAKE_CXX_COMPILER}" "$@" From 31e665ab8bc737bfa248d25434f8256ceb44ad48 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 10 Oct 2018 16:49:20 -0700 Subject: [PATCH 077/276] CR changes - runtime property data rework --- scripts/system/html/js/entityProperties.js | 1554 +++++++++++--------- 1 file changed, 850 insertions(+), 704 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 46f16dd0a0..2841414d87 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -6,8 +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 -/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, - HifiEntityUI, JSONEditor, openEventBridge, setTimeout, window, _ $ */ +/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, + EventBridge, JSONEditor, openEventBridge, setTimeout, window, _ $ */ const ICON_FOR_TYPE = { Box: "V", @@ -40,6 +40,7 @@ const GROUPS = [ label: NO_SELECTION, type: "icon", icons: ICON_FOR_TYPE, + defaultValue: "Box", propertyName: "type", }, { @@ -79,7 +80,10 @@ const GROUPS = [ { label: "Shape", type: "dropdown", - options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, + options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", + Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", + Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", + Circle: "Circle", Quad: "Quad" }, propertyName: "shape", }, { @@ -208,7 +212,8 @@ const GROUPS = [ }, { type: "buttons", - buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], + buttons: [ { id: "copy", label: "Copy URL To Ambient", + className: "black", onClick: copySkyboxURLToAmbientURL } ], propertyName: "copyURLToAmbient", showPropertyRule: { "skyboxMode": "enabled" }, }, @@ -367,7 +372,9 @@ const GROUPS = [ { label: "Collision Shape", type: "dropdown", - options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, + options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , + "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , + "static-mesh": "Exact - All polygons (non-dynamic only)" }, propertyName: "shapeType", }, { @@ -467,8 +474,8 @@ const GROUPS = [ { label: "Light Color", type: "color", - propertyName: "lightColor", // this actually shares "color" property with shape Color - // but separating naming here to separate property fields + propertyName: "lightColor", // this actually shares "color" property with shape Color but + // separating naming here to distinguish property element/data }, { label: "Intensity", @@ -521,8 +528,8 @@ const GROUPS = [ label: "Material Data", type: "textarea", buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, - { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, + { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], propertyName: "materialData", }, { @@ -613,7 +620,7 @@ const GROUPS = [ defaultValue: 100, unit: "%", buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, - { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], + { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], propertyName: "scale", }, { @@ -628,8 +635,8 @@ const GROUPS = [ { label: "Align", type: "buttons", - buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, - { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], + buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, + { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], propertyName: "alignToGrid", }, ] @@ -642,8 +649,8 @@ const GROUPS = [ { label: "Collides", type: "bool", - propertyName: "collisionless", inverse: true, + propertyName: "collisionless", column: -1, // before two columns div }, { @@ -655,14 +662,14 @@ const GROUPS = [ { label: "Collides With", type: "sub-header", - propertyName: "collidesWithHeader", + propertyName: "collidesWithHeader", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, column: 1, }, { label: "", type: "sub-header", - propertyName: "collidesWithHeaderHelper", + propertyName: "collidesWithHeaderHelper", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, column: 2, }, @@ -711,6 +718,7 @@ const GROUPS = [ type: "string", propertyName: "collisionSoundURL", showPropertyRule: { "collisionless": "false" }, + // having no column number means place this after two columns div }, ] }, @@ -772,7 +780,7 @@ const GROUPS = [ showPropertyRule: { "cloneable": "true" }, column: 1, }, - { + { // below properties having no column number means place them after two columns div label: "Can cast shadow", type: "bool", propertyName: "canCastShadow", @@ -799,8 +807,8 @@ const GROUPS = [ label: "User Data", type: "textarea", buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, - { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], propertyName: "userData", }, ] @@ -898,11 +906,9 @@ const MATERIAL_PREFIX_STRING = "mat::"; const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; var elGroups = {}; -var elPropertyElements = {}; -var showPropertyRules = {}; -var subProperties = []; -var icons = {}; +var properties = {}; var colorPickers = {}; +var selectedEntityProperties; var lastEntityID = null; function debugPrint(message) { @@ -914,6 +920,17 @@ function debugPrint(message) { ); } + +// GENERAL PROPERTY/GROUP FUNCTIONS + +function getPropertyElement(propertyName) { + return properties[propertyName].el; +} + +function getPropertyElementID(propertyName) { + return "property-" + propertyName; +} + function enableChildren(el, selector) { var elSelectors = el.querySelectorAll(selector); for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { @@ -931,7 +948,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); enableChildren(document, ".colpick"); - var elLocked = elPropertyElements["locked"]; + var elLocked = getPropertyElement("locked"); if (elLocked.checked === false) { removeStaticUserData(); @@ -945,7 +962,7 @@ function disableProperties() { for (var pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - var elLocked = elPropertyElements["locked"]; + var elLocked = getPropertyElement("locked"); if (elLocked.checked === true) { if ($('#property-userData-editor').css('display') === "block") { @@ -957,34 +974,148 @@ function disableProperties() { } } +function showPropertyElement(propertyName, show) { + let elProperty = properties[propertyName].el; + let elNode = elProperty; + if (elNode.nodeName !== "DIV") { + let elParent = elProperty.parentNode; + if (elParent === undefined && elProperty instanceof Array) { + elParent = elProperty[0].parentNode; + } + if (elParent !== undefined) { + elNode = elParent; + } + } + elNode.style.display = show ? "table" : "none"; +} + +function resetProperties() { + for (let propertyName in properties) { + let elProperty = properties[propertyName].el; + let propertyData = properties[propertyName].data; + + switch (propertyData.type) { + case 'string': { + elProperty.value = ""; + break; + } + case 'bool': { + elProperty.checked = false; + break; + } + case 'number': { + if (propertyData.defaultValue !== undefined) { + elProperty.value = propertyData.defaultValue; + } else { + elProperty.value = ""; + } + break; + } + case 'vec3': + case 'vec2': { + // vec2/vec3 are array of 2/3 elInput numbers + elProperty[0].value = ""; + elProperty[1].value = ""; + if (elProperty[2] !== undefined) { + elProperty[2].value = ""; + } + break; + } + case 'color': { + // color is array of color picker and 3 elInput numbers + elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elProperty[1].value = ""; + elProperty[2].value = ""; + elProperty[3].value = ""; + break; + } + case 'dropdown': { + elProperty.value = ""; + setDropdownText(elProperty); + break; + } + case 'textarea': { + elProperty.value = ""; + setTextareaScrolling(elProperty); + break; + } + case 'icon': { + // icon is array of elSpan (icon glyph) and elLabel + if (propertyData.defaultValue !== undefined) { + elProperty[0].innerHTML = propertyData.icons[propertyData.defaultValue]; + elProperty[0].style.display = "inline-block"; + elProperty[1].innerHTML = propertyData.defaultValue; + } + break; + } + } + + let showPropertyRules = properties[propertyName].showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToHide in showPropertyRules) { + showPropertyElement(propertyToHide, false); + } + } + } +} + +function showGroupsForType(type) { + if (type === "Box" || type === "Sphere") { + type = "Shape"; + } + + let typeGroups = GROUPS_PER_TYPE[type]; + + for (let groupKey in elGroups) { + let elGroup = elGroups[groupKey]; + if (typeGroups && typeGroups.indexOf(groupKey) > -1) { + elGroup.style.display = "block"; + } else { + elGroup.style.display = "none"; + } + } +} + + + +// PROPERTY UPDATE FUNCTIONS + function updateProperty(propertyName, propertyValue) { - var properties = {}; + let propertyUpdate = {}; + // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times let splitPropertyName = propertyName.split('.'); if (splitPropertyName.length > 1) { let propertyGroupName = splitPropertyName[0]; let subPropertyName = splitPropertyName[1]; - properties[propertyGroupName] = {}; + propertyUpdate[propertyGroupName] = {}; if (splitPropertyName.length === 3) { let subSubPropertyName = splitPropertyName[2]; - properties[propertyGroupName][subPropertyName] = {}; - properties[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; + propertyUpdate[propertyGroupName][subPropertyName] = {}; + propertyUpdate[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; } else { - properties[propertyGroupName][subPropertyName] = propertyValue; + propertyUpdate[propertyGroupName][subPropertyName] = propertyValue; } } else { - properties[propertyName] = propertyValue; + propertyUpdate[propertyName] = propertyValue; } - updateProperties(properties); + updateProperties(propertyUpdate); } -function updateProperties(properties) { +function updateProperties(propertiesToUpdate) { EventBridge.emitWebEvent(JSON.stringify({ id: lastEntityID, type: "update", - properties: properties + properties: propertiesToUpdate })); } + +function createEmitTextPropertyUpdateFunction(propertyName) { + return function() { + updateProperty(propertyName, this.value); + }; +} + function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { return function() { updateProperty(propertyName, inverse ? !this.checked : this.checked); @@ -994,67 +1125,50 @@ function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { decimals = ((decimals === undefined) ? 4 : decimals); return function() { - var value = parseFloat(this.value).toFixed(decimals); + let value = parseFloat(this.value).toFixed(decimals); updateProperty(propertyName, value); }; } -function createImageURLUpdateFunction(propertyName) { - return function () { - var newTextures = JSON.stringify({ "tex.picture": this.value }); - updateProperty(propertyName, newTextures); - }; -} - -function createEmitTextPropertyUpdateFunction(propertyName) { - return function() { - updateProperty(propertyName, this.value); - }; -} - function createEmitVec2PropertyUpdateFunction(property, elX, elY) { return function () { - var properties = {}; - properties[property] = { + let newValue = { x: elX.value, y: elY.value }; - updateProperties(properties); + updateProperty(property, newValue); }; } function createEmitVec2PropertyUpdateFunctionWithMultiplier(property, elX, elY, multiplier) { return function () { - var properties = {}; - properties[property] = { + let newValue = { x: elX.value * multiplier, y: elY.value * multiplier }; - updateProperties(properties); + updateProperty(property, newValue); }; } function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { return function() { - var properties = {}; - properties[property] = { + let newValue = { x: elX.value, y: elY.value, z: elZ ? elZ.value : 0 }; - updateProperties(properties); + updateProperty(property, newValue); }; } function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) { return function() { - var properties = {}; - properties[property] = { + let newValue = { x: elX.value * multiplier, y: elY.value * multiplier, z: elZ.value * multiplier }; - updateProperties(properties); + updateProperty(property, newValue); }; } @@ -1064,23 +1178,13 @@ function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) }; } -function emitColorPropertyUpdate(property, red, green, blue, group) { - var properties = {}; - if (group) { - properties[group] = {}; - properties[group][property] = { - red: red, - green: green, - blue: blue - }; - } else { - properties[property] = { - red: red, - green: green, - blue: blue - }; - } - updateProperties(properties); +function emitColorPropertyUpdate(property, red, green, blue) { + let newValue = { + red: red, + green: green, + blue: blue + }; + updateProperty(property, newValue); } function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { @@ -1095,8 +1199,440 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen updateProperty(propertyName, propertyValue); } +function createImageURLUpdateFunction(propertyName) { + return function () { + var newTextures = JSON.stringify({ "tex.picture": this.value }); + updateProperty(propertyName, newTextures); + }; +} + + + +// PROPERTY ELEMENT CREATION FUNCTIONS + +function createStringProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property text"); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("type", "text"); + if (propertyData.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, propertyElementID, propertyData.buttons, false); + } + + properties[propertyName].el = elInput; +} + +function createBoolProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property checkbox"); + + if (propertyData.glyph !== undefined) { + elLabel.innerText = " " + propertyData.label; + let elSpan = document.createElement('span'); + elSpan.innerHTML = propertyData.glyph; + elLabel.insertBefore(elSpan, elLabel.firstChild); + } + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("type", "checkbox"); + + elProperty.appendChild(elInput); + elProperty.appendChild(elLabel); + + let subPropertyOf = propertyData.subPropertyOf; + if (subPropertyOf !== undefined) { + elInput.addEventListener('change', function() { + updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName); + }); + } else { + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse)); + } + + properties[propertyName].el = elInput; +} + +function createVec3Property(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property " + propertyData.vec3Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(propertyData.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[0], + propertyData.min, propertyData.max, propertyData.step); + let elInputY = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[1], + propertyData.min, propertyData.max, propertyData.step); + let elInputZ = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[2], + propertyData.min, propertyData.max, propertyData.step); + + let inputChangeFunction; + if (propertyData.multiplier !== undefined) { + inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, + elInputZ, propertyData.multiplier); + } else { + inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); + } + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + elInputZ.addEventListener('change', inputChangeFunction); + + properties[propertyName].el = [ elInputX, elInputY, elInputZ ]; +} + +function createVec2Property(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property " + propertyData.vec2Type + " fstuple"); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + addUnit(propertyData.unit, elLabel); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputX = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[0], + propertyData.min, propertyData.max, propertyData.step); + let elInputY = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[1], + propertyData.min, propertyData.max, propertyData.step); + + let inputChangeFunction; + if (propertyData.multiplier !== undefined) { + inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, + propertyData.multiplier); + } else { + inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY); + } + elInputX.addEventListener('change', inputChangeFunction); + elInputY.addEventListener('change', inputChangeFunction); + + properties[propertyName].el = [elInputX, elInputY]; +} + +function createColorProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property rgb fstuple"); + + let elColorPicker = document.createElement('div'); + elColorPicker.setAttribute("class", "color-picker"); + elColorPicker.setAttribute("id", propertyElementID); + + let elTuple = document.createElement('div'); + elTuple.setAttribute("class", "tuple"); + + elProperty.appendChild(elColorPicker); + elProperty.appendChild(elLabel); + elProperty.appendChild(elTuple); + + let elInputR = createTupleNumberInput(elTuple, propertyElementID, "red", 0, 255, 1); + let elInputG = createTupleNumberInput(elTuple, propertyElementID, "green", 0, 255, 1); + let elInputB = createTupleNumberInput(elTuple, propertyElementID, "blue", 0, 255, 1); + + let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); + elInputR.addEventListener('change', inputChangeFunction); + elInputG.addEventListener('change', inputChangeFunction); + elInputB.addEventListener('change', inputChangeFunction); + + let colorPickerID = "#" + propertyElementID; + colorPickers[colorPickerID] = $(colorPickerID).colpick({ + colorScheme: 'dark', + layout: 'hex', + color: '000000', + submit: false, // We don't want to have a submission button + onShow: function(colpick) { + $(colorPickerID).attr('active', 'true'); + // The original color preview within the picker needs to be updated on show because + // prior to the picker being shown we don't have access to the selections' starting color. + colorPickers[colorPickerID].colpickSetColor({ + "r": elInputR.value, + "g": elInputG.value, + "b": elInputB.value + }); + }, + onHide: function(colpick) { + $(colorPickerID).attr('active', 'false'); + }, + onChange: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + } + }); + + properties[propertyName].el = [elColorPicker, elInputR, elInputG, elInputB]; +} + +function createDropdownProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property dropdown"); + + let elInput = document.createElement('select'); + elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("propertyName", propertyName); + + for (let optionKey in propertyData.options) { + let option = document.createElement('option'); + option.value = optionKey; + option.text = propertyData.options[optionKey]; + elInput.add(option); + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + properties[propertyName].el = elInput; +} + +function createNumberProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property number"); + + addUnit(propertyData.unit, elLabel); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("type", "number"); + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + } + + let defaultValue = propertyData.defaultValue; + if (defaultValue !== undefined) { + elInput.setAttribute("defaultValue", defaultValue); + elInput.value = defaultValue; + } + + let fixedDecimals = propertyData.fixedDecimals !== undefined ? propertyData.fixedDecimals : 0; + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, propertyElementID, propertyData.buttons, true); + } + + properties[propertyName].el = elInput; +} + +function createTextareaProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property textarea"); + + elProperty.appendChild(elLabel); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, propertyElementID, propertyData.buttons, true); + } + + let elInput = document.createElement('textarea'); + elInput.setAttribute("id", propertyElementID); + if (propertyData.readOnly) { + elInput.readOnly = true; + } + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + + elProperty.appendChild(elInput); + + properties[propertyName].el = elInput; +} + +function createIconProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property value"); + + elLabel.setAttribute("id", propertyElementID); + elLabel.innerHTML = " " + propertyData.label; + + let elSpan = document.createElement('span'); + elSpan.setAttribute("id", propertyElementID + "-icon"); + + elProperty.appendChild(elSpan); + elProperty.appendChild(elLabel); + + properties[propertyName].el = [ elSpan, elLabel ]; +} + +function createButtonsProperty(elProperty, elLabel, propertyData) { + let propertyName = propertyData.propertyName; + let propertyElementID = getPropertyElementID(propertyName); + + elProperty.setAttribute("class", "property text"); + + let hasLabel = propertyData.label !== undefined; + if (hasLabel) { + elProperty.appendChild(elLabel); + } + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, propertyElementID, propertyData.buttons, hasLabel); + } + + properties[propertyName].el = elProperty; +} + +function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { + let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; + elLabel.setAttribute("for", elementPropertyID); + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementPropertyID); + elInput.setAttribute("type", "number"); + elInput.setAttribute("class", subLabel); + if (min !== undefined) { + elInput.setAttribute("min", min); + } + if (max !== undefined) { + elInput.setAttribute("max", max); + } + if (step !== undefined) { + elInput.setAttribute("step", step); + } + elDiv.appendChild(elInput); + elDiv.appendChild(elLabel); + elTuple.appendChild(elDiv); + return elInput; +} + +function addUnit(unit, elLabel) { + if (unit !== undefined) { + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "unit"); + elSpan.innerHTML = unit; + elLabel.appendChild(elSpan); + } +} + +function addButtons(elProperty, propertyID, buttons, newRow) { + let elDiv = document.createElement('div'); + elDiv.setAttribute("class", "row"); + + buttons.forEach(function(button) { + let elButton = document.createElement('input'); + elButton.setAttribute("type", "button"); + elButton.setAttribute("class", button.className); + elButton.setAttribute("id", propertyID + "-button-" + button.id); + elButton.setAttribute("value", button.label); + elButton.addEventListener("click", button.onClick); + if (newRow) { + elDiv.appendChild(elButton); + } else { + elProperty.appendChild(elButton); + } + }); + + if (newRow) { + elProperty.appendChild(document.createElement('br')); + elProperty.appendChild(elDiv); + } +} + + + +// BUTTON CALLBACKS + +function rescaleDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "rescaleDimensions", + percentage: parseFloat(document.getElementById("property-scale").value) + })); +} + +function moveSelectionToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveSelectionToGrid" + })); +} + +function moveAllToGrid() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "moveAllToGrid" + })); +} + +function resetToNaturalDimensions() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "resetToNaturalDimensions" + })); +} + +function reloadScripts() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadClientScripts" + })); +} + +function reloadServerScripts() { + // invalidate the current status (so that same-same updates can still be observed visually) + document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; + EventBridge.emitWebEvent(JSON.stringify({ + type: "action", + action: "reloadServerScripts" + })); +} + +function copySkyboxURLToAmbientURL() { + let skyboxURL = getPropertyElement("skybox.url").value; + getPropertyElement("ambientLight.ambientURL").value = skyboxURL; + updateProperty("ambientLight.ambientURL", skyboxURL); +} + + + +// USER DATA FUNCTIONS + function clearUserData() { - let elUserData = elPropertyElements["userData"]; + let elUserData = getPropertyElement("userData"); deleteJSONEditor(); elUserData.value = ""; showUserDataTextArea(); @@ -1148,7 +1684,7 @@ function setUserDataFromEditor(noUpdate) { } function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, removeKeys) { - var properties = {}; + var propertyUpdate = {}; var parsedData = {}; var keysToBeRemoved = removeKeys ? removeKeys : []; try { @@ -1192,14 +1728,14 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r delete parsedData[groupName]; } if (Object.keys(parsedData).length > 0) { - properties.userData = JSON.stringify(parsedData); + propertyUpdate.userData = JSON.stringify(parsedData); } else { - properties.userData = ''; + propertyUpdate.userData = ''; } - userDataElement.value = properties.userData; + userDataElement.value = propertyUpdate.userData; - updateProperties(properties); + updateProperties(propertyUpdate); } function userDataChanger(groupName, keyName, values, userDataElement, defaultValue, removeKeys) { var val = {}, def = {}; @@ -1313,8 +1849,12 @@ function saveJSONUserData(noUpdate) { }, EDITOR_TIMEOUT_DURATION); } + + +// MATERIAL DATA FUNCTIONS + function clearMaterialData() { - let elMaterialData = elPropertyElements["materialData"]; + let elMaterialData = getPropertyElement("materialData"); deleteJSONMaterialEditor(); elMaterialData.value = ""; showMaterialDataTextArea(); @@ -1479,7 +2019,8 @@ function bindAllNonJSONEditorElements() { // TODO FIXME: (JSHint) Functions declared within loops referencing // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { - if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { + if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || + e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { return; } else { if ($('#property-userData-editor').css('height') !== "0px") { @@ -1493,14 +2034,41 @@ function bindAllNonJSONEditorElements() { } } -function unbindAllInputs() { - var inputs = $('input'); - var i; - for (i = 0; i < inputs.length; i++) { - var input = inputs[i]; - var field = $(input); - field.unbind(); + + +// DROPDOWNS / TEXTAREAS / PARENT MATERIAL NAME FUNCTIONS + +function setDropdownText(dropdown) { + let lis = dropdown.parentNode.getElementsByTagName("li"); + let text = ""; + for (let i = 0; i < lis.length; i++) { + if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { + text = lis[i].textContent; + } } + dropdown.firstChild.textContent = text; +} + +function toggleDropdown(event) { + let element = event.target; + if (element.nodeName !== "DT") { + element = element.parentNode; + } + element = element.parentNode; + let isDropped = element.getAttribute("dropped"); + element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); +} + +function setDropdownValue(event) { + let dt = event.target.parentNode.parentNode.previousSibling; + dt.value = event.target.getAttribute("value"); + dt.firstChild.textContent = event.target.textContent; + + dt.parentNode.setAttribute("dropped", "false"); + + let evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", true, true); + dt.dispatchEvent(evt); } function setTextareaScrolling(element) { @@ -1520,183 +2088,7 @@ function showParentMaterialNameBox(number, elNumber, elString) { } } -function rescaleDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "rescaleDimensions", - percentage: parseFloat(document.getElementById("property-scale").value) - })); -} -function moveSelectionToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveSelectionToGrid" - })); -} - -function moveAllToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveAllToGrid" - })); -} - -function resetToNaturalDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "resetToNaturalDimensions" - })); -} - -function reloadScripts() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadClientScripts" - })); -} - -function reloadServerScripts() { - // invalidate the current status (so that same-same updates can still be observed visually) - document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadServerScripts" - })); -} - -function copySkyboxURLToAmbientURL() { - let skyboxURL = elPropertyElements["skybox.url"].value; - elPropertyElements["ambientLight.ambientURL"].value = skyboxURL; - updateProperty("ambientLight.ambientURL", skyboxURL); -} - -function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { - let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); - let elDiv = document.createElement('div'); - let elLabel = document.createElement('label'); - elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; - elLabel.setAttribute("for", elementPropertyID); - let elInput = document.createElement('input'); - elInput.setAttribute("id", elementPropertyID); - elInput.setAttribute("type", "number"); - elInput.setAttribute("class", subLabel); - if (min !== undefined) { - elInput.setAttribute("min", min); - } - if (max !== undefined) { - elInput.setAttribute("max", max); - } - if (step !== undefined) { - elInput.setAttribute("step", step); - } - elDiv.appendChild(elInput); - elDiv.appendChild(elLabel); - elTuple.appendChild(elDiv); - return elInput; -} - -function addUnit(unit, elLabel) { - if (unit !== undefined) { - let elSpan = document.createElement('span'); - elSpan.setAttribute("class", "unit"); - elSpan.innerHTML = unit; - elLabel.appendChild(elSpan); - } -} - -function addButtons(elProperty, propertyID, buttons, newRow) { - let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "row"); - - buttons.forEach(function(button) { - let elButton = document.createElement('input'); - elButton.setAttribute("type", "button"); - elButton.setAttribute("class", button.className); - elButton.setAttribute("id", propertyID + "-button-" + button.id); - elButton.setAttribute("value", button.label); - elButton.addEventListener("click", button.onClick); - if (newRow) { - elDiv.appendChild(elButton); - } else { - elProperty.appendChild(elButton); - } - }); - - if (newRow) { - elProperty.appendChild(document.createElement('br')); - elProperty.appendChild(elDiv); - } -} - -function showGroupsForType(type) { - if (type === "Box" || type === "Sphere") { - type = "Shape"; - } - - let typeGroups = GROUPS_PER_TYPE[type]; - - for (let groupKey in elGroups) { - let elGroup = elGroups[groupKey]; - if (typeGroups && typeGroups.indexOf(groupKey) > -1) { - elGroup.style.display = "block"; - } else { - elGroup.style.display = "none"; - } - } -} - -function resetProperties() { - for (let propertyName in elPropertyElements) { - let elProperty = elPropertyElements[propertyName]; - if (elProperty instanceof Array) { - if (elProperty.length === 2 || elProperty.length === 3) { - // vec2/vec3 are array of 2/3 input numbers - elProperty[0].value = ""; - elProperty[1].value = ""; - if (elProperty[2] !== undefined) { - elProperty[2].value = ""; - } - } else if (elProperty.length === 4) { - // color is array of color picker and 3 input numbers - elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elProperty[1].value = ""; - elProperty[2].value = ""; - elProperty[3].value = ""; - } - } else if (elProperty.getAttribute("type") === "number") { - if (elProperty.getAttribute("defaultValue")) { - elProperty.value = elProperty.getAttribute("defaultValue"); - } else { - elProperty.value = ""; - } - } else if (elProperty.getAttribute("type") === "checkbox") { - elProperty.checked = false; - } else { - elProperty.value = ""; - } - } - - for (let showPropertyRule in showPropertyRules) { - let propertyShowRules = showPropertyRules[showPropertyRule]; - for (let propertyToHide in propertyShowRules) { - let elPropertyToHide = elPropertyElements[propertyToHide]; - if (elPropertyToHide) { - let nodeToHide = elPropertyToHide; - if (elPropertyToHide.nodeName !== "DIV") { - let parentNode = elPropertyToHide.parentNode; - if (parentNode === undefined && elPropertyToHide instanceof Array) { - parentNode = elPropertyToHide[0].parentNode; - } - if (parentNode !== undefined) { - nodeToHide = parentNode; - } - } - nodeToHide.style.display = "none"; - } - } - } -} function loaded() { openEventBridge(function() { @@ -1729,8 +2121,8 @@ function loaded() { group.properties.forEach(function(property) { let propertyType = property.type; let propertyName = property.propertyName; - let propertyID = "property-" + propertyName; - propertyID = propertyID.replace(".", "-"); + let propertyElementID = getPropertyElementID(propertyName); + propertyElementID = propertyElementID.replace(".", "-"); let elProperty; if (propertyType === "sub-header") { @@ -1739,7 +2131,7 @@ function loaded() { elProperty.setAttribute("class", "sub-section-header"); } else { elProperty = document.createElement('div'); - elProperty.setAttribute("id", "div-" + propertyID); + elProperty.setAttribute("id", "div-" + propertyElementID); } if (group.twoColumn && property.column !== undefined && property.column !== -1) { @@ -1766,294 +2158,58 @@ function loaded() { let elLabel = document.createElement('label'); elLabel.innerText = property.label; - elLabel.setAttribute("for", propertyID); + elLabel.setAttribute("for", propertyElementID); + + properties[propertyName] = { data: property }; switch (propertyType) { - case 'vec3': { - elProperty.setAttribute("class", "property " + property.vec3Type + " fstuple"); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - addUnit(property.unit, elLabel); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); - let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); - let elInputZ = createTupleNumberInput(elTuple, propertyID, property.subLabels[2], property.min, property.max, property.step); - - let inputChangeFunction; - let multiplier = 1; - if (property.multiplier !== undefined) { - multiplier = property.multiplier; - inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); - } else { - inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, elInputZ, property.multiplier); - } - elInputX.setAttribute("multiplier", multiplier); - elInputY.setAttribute("multiplier", multiplier); - elInputZ.setAttribute("multiplier", multiplier); - elInputX.addEventListener('change', inputChangeFunction); - elInputY.addEventListener('change', inputChangeFunction); - elInputZ.addEventListener('change', inputChangeFunction); - - elPropertyElements[propertyName] = [elInputX, elInputY, elInputZ]; - break; - } - case 'vec2': { - elProperty.setAttribute("class", "property " + property.vec2Type + " fstuple"); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - addUnit(property.unit, elLabel); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputX = createTupleNumberInput(elTuple, propertyID, property.subLabels[0], property.min, property.max, property.step); - let elInputY = createTupleNumberInput(elTuple, propertyID, property.subLabels[1], property.min, property.max, property.step); - - let inputChangeFunction; - let multiplier = 1; - if (property.multiplier !== undefined) { - multiplier = property.multiplier; - inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY); - } else { - inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, property.multiplier); - } - elInputX.setAttribute("multiplier", multiplier); - elInputY.setAttribute("multiplier", multiplier); - elInputX.addEventListener('change', inputChangeFunction); - elInputY.addEventListener('change', inputChangeFunction); - - elPropertyElements[propertyName] = [elInputX, elInputY]; - break; - } - case 'color': { - elProperty.setAttribute("class", "property rgb fstuple"); - - let elColorPicker = document.createElement('div'); - elColorPicker.setAttribute("class", "color-picker"); - elColorPicker.setAttribute("id", propertyID); - - let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); - - elProperty.appendChild(elColorPicker); - elProperty.appendChild(elLabel); - elProperty.appendChild(elTuple); - - let elInputR = createTupleNumberInput(elTuple, propertyID, "red", 0, 255, 1); - let elInputG = createTupleNumberInput(elTuple, propertyID, "green", 0, 255, 1); - let elInputB = createTupleNumberInput(elTuple, propertyID, "blue", 0, 255, 1); - - let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); - elInputR.addEventListener('change', inputChangeFunction); - elInputG.addEventListener('change', inputChangeFunction); - elInputB.addEventListener('change', inputChangeFunction); - - let colorPickerID = "#" + propertyID; - colorPickers[colorPickerID] = $(colorPickerID).colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - $(colorPickerID).attr('active', 'true'); - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers[colorPickerID].colpickSetColor({ - "r": elInputR.value, - "g": elInputG.value, - "b": elInputB.value - }); - }, - onHide: function(colpick) { - $(colorPickerID).attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - } - }); - - elPropertyElements[propertyName] = [elColorPicker, elInputR, elInputG, elInputB]; - break; - } case 'string': { - elProperty.setAttribute("class", "property text"); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "text"); - if (property.readOnly) { - elInput.readOnly = true; - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, false); - } - - elPropertyElements[propertyName] = elInput; + createStringProperty(elProperty, elLabel, property); break; } case 'bool': { - elProperty.setAttribute("class", "property checkbox"); - - if (property.glyph !== undefined) { - elLabel.innerText = " " + property.label; - let elSpan = document.createElement('span'); - elSpan.innerHTML = property.glyph; - elLabel.insertBefore(elSpan, elLabel.firstChild); - } - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "checkbox"); - - let inverse = property.inverse; - elInput.setAttribute("inverse", inverse ? "true" : "false"); - - elProperty.appendChild(elInput); - elProperty.appendChild(elLabel); - - let subPropertyOf = property.subPropertyOf; - if (subPropertyOf !== undefined) { - elInput.setAttribute("subPropertyOf", subPropertyOf); - elPropertyElements[propertyName] = elInput; - subProperties.push(propertyName); - elInput.addEventListener('change', function() { - updateCheckedSubProperty(subPropertyOf, properties[subPropertyOf], elInput, propertyName); - }); - } else { - elPropertyElements[propertyName] = elInput; - elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, inverse)); - } - break; - } - case 'dropdown': { - elProperty.setAttribute("class", "property dropdown"); - - let elInput = document.createElement('select'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("propertyName", propertyName); - - for (let optionKey in property.options) { - let option = document.createElement('option'); - option.value = optionKey; - option.text = property.options[optionKey]; - elInput.add(option); - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - elPropertyElements[propertyName] = elInput; + createBoolProperty(elProperty, elLabel, property); break; } case 'number': { - elProperty.setAttribute("class", "property number"); - - addUnit(property.unit, elLabel); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyID); - elInput.setAttribute("type", "number"); - if (property.min !== undefined) { - elInput.setAttribute("min", property.min); - } - if (property.max !== undefined) { - elInput.setAttribute("max", property.max); - } - if (property.step !== undefined) { - elInput.setAttribute("step", property.step); - } - - let fixedDecimals = property.fixedDecimals !== undefined ? property.fixedDecimals : 0; - elInput.setAttribute("fixedDecimals", fixedDecimals); - - let defaultValue = property.defaultValue; - if (defaultValue !== undefined) { - elInput.setAttribute("defaultValue", defaultValue); - elInput.value = defaultValue; - } - - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, true); - } - - elPropertyElements[propertyName] = elInput; + createNumberProperty(elProperty, elLabel, property); + break; + } + case 'vec3': { + createVec3Property(elProperty, elLabel, property); + break; + } + case 'vec2': { + createVec2Property(elProperty, elLabel, property); + break; + } + case 'color': { + createColorProperty(elProperty, elLabel, property); + break; + } + case 'dropdown': { + createDropdownProperty(elProperty, elLabel, property); break; } case 'textarea': { - elProperty.setAttribute("class", "property textarea"); - - elProperty.appendChild(elLabel); - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, true); - } - - let elInput = document.createElement('textarea'); - elInput.setAttribute("id", propertyID); - if (property.readOnly) { - elInput.readOnly = true; - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); - - elProperty.appendChild(elInput); - elPropertyElements[propertyName] = elInput; + createTextareaProperty(elProperty, elLabel, property); break; } case 'icon': { - elProperty.setAttribute("class", "property value"); - - elLabel.setAttribute("id", propertyID); - elLabel.innerHTML = " " + property.label; - - let elSpan = document.createElement('span'); - elSpan.setAttribute("id", propertyID + "-icon"); - icons[propertyName] = property.icons; - - elProperty.appendChild(elSpan); - elProperty.appendChild(elLabel); - elPropertyElements[propertyName] = [ elSpan, elLabel ]; + createIconProperty(elProperty, elLabel, property); break; } case 'buttons': { - elProperty.setAttribute("class", "property text"); - - let hasLabel = property.label !== undefined; - if (hasLabel) { - elProperty.appendChild(elLabel); - } - - if (property.buttons !== undefined) { - addButtons(elProperty, propertyID, property.buttons, hasLabel); - } - - elPropertyElements[propertyName] = elProperty; + createButtonsProperty(elProperty, elLabel, property); break; } case 'sub-header': { - if (propertyName !== undefined) { - elPropertyElements[propertyName] = elProperty; - } + properties[propertyName].el = elProperty; + break; + } + default: { + console.log("EntityProperties - Unknown property type " + + propertyType + " set to property " + propertyName); break; } } @@ -2062,10 +2218,13 @@ function loaded() { if (showPropertyRule !== undefined) { let dependentProperty = Object.keys(showPropertyRule)[0]; let dependentPropertyValue = showPropertyRule[dependentProperty]; - if (!showPropertyRules[dependentProperty]) { - showPropertyRules[dependentProperty] = []; + if (properties[dependentProperty] === undefined) { + properties[dependentProperty] = {}; } - showPropertyRules[dependentProperty][propertyName] = dependentPropertyValue; + if (properties[dependentProperty].showPropertyRules === undefined) { + properties[dependentProperty].showPropertyRules = {}; + } + properties[dependentProperty].showPropertyRules[propertyName] = dependentPropertyValue; } }); @@ -2073,7 +2232,6 @@ function loaded() { }); if (window.EventBridge !== undefined) { - var properties; EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); if (data.type === "server_script_status") { @@ -2112,21 +2270,21 @@ function loaded() { } } - elPropertyElements["type"][0].style.display = "none"; - elPropertyElements["type"][1].innerHTML = NO_SELECTION; + resetProperties(); + + getPropertyElement("type")[0].style.display = "none"; + getPropertyElement("type")[1].innerHTML = NO_SELECTION; elPropertiesList.className = ''; showGroupsForType("None"); - - resetProperties(); deleteJSONEditor(); - elPropertyElements["userData"].value = ""; + getPropertyElement("userData").value = ""; showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); deleteJSONMaterialEditor(); - elPropertyElements["materialData"].value = ""; + getPropertyElement("materialData").value = ""; showMaterialDataTextArea(); showSaveMaterialDataButton(); showNewJSONMaterialEditorButton(); @@ -2157,18 +2315,19 @@ function loaded() { type = selections[0].properties.type; } - elPropertyElements["type"][0].innerHTML = ICON_FOR_TYPE[type]; - elPropertyElements["type"][0].style.display = "inline-block"; - elPropertyElements["type"][1].innerHTML = type + " (" + data.selections.length + ")"; + resetProperties(); + + getPropertyElement("type")[0].innerHTML = ICON_FOR_TYPE[type]; + getPropertyElement("type")[0].style.display = "inline-block"; + getPropertyElement("type")[1].innerHTML = type + " (" + data.selections.length + ")"; elPropertiesList.className = ''; showGroupsForType(type); - resetProperties(); disableProperties(); } else { - properties = data.selections[0].properties; + selectedEntityProperties = data.selections[0].properties; - if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) { + if (lastEntityID !== '"' + selectedEntityProperties.id + '"' && lastEntityID !== null) { if (editor !== null) { saveUserData(); } @@ -2177,132 +2336,144 @@ function loaded() { } } - let doSelectElement = lastEntityID === '"' + properties.id + '"'; + let doSelectElement = lastEntityID === '"' + selectedEntityProperties.id + '"'; // the event bridge and json parsing handle our avatar id string differently. - lastEntityID = '"' + properties.id + '"'; + lastEntityID = '"' + selectedEntityProperties.id + '"'; // HTML workaround since image is not yet a separate entity type let IMAGE_MODEL_NAME = 'default-image-model.fbx'; - if (properties.type === "Model") { - let urlParts = properties.modelURL.split('/'); + if (selectedEntityProperties.type === "Model") { + let urlParts = selectedEntityProperties.modelURL.split('/'); let propsFilename = urlParts[urlParts.length - 1]; if (propsFilename === IMAGE_MODEL_NAME) { - properties.type = "Image"; + selectedEntityProperties.type = "Image"; } } // Create class name for css ruleset filtering - elPropertiesList.className = properties.type + 'Menu'; - showGroupsForType(properties.type); + elPropertiesList.className = selectedEntityProperties.type + 'Menu'; + showGroupsForType(selectedEntityProperties.type); - for (let propertyName in elPropertyElements) { - let elProperty = elPropertyElements[propertyName]; + for (let propertyName in properties) { + let property = properties[propertyName]; + let elProperty = property.el; + let propertyData = property.data; + // if this is a compound property name (i.e. animation.running) + // then split it by . up to 3 times to find property value let propertyValue; let splitPropertyName = propertyName.split('.'); if (splitPropertyName.length > 1) { let propertyGroupName = splitPropertyName[0]; let subPropertyName = splitPropertyName[1]; - if (properties[propertyGroupName] === undefined || properties[propertyGroupName][subPropertyName] === undefined) { + let groupProperties = selectedEntityProperties[propertyGroupName]; + if (groupProperties === undefined || groupProperties[subPropertyName] === undefined) { continue; } if (splitPropertyName.length === 3) { let subSubPropertyName = splitPropertyName[2]; - propertyValue = properties[propertyGroupName][subPropertyName][subSubPropertyName]; + propertyValue = groupProperties[subPropertyName][subSubPropertyName]; } else { - propertyValue = properties[propertyGroupName][subPropertyName]; + propertyValue = groupProperties[subPropertyName]; } } else { - propertyValue = properties[propertyName]; + propertyValue = selectedEntityProperties[propertyName]; } // workaround for shape Color & Light Color property fields sharing same property value "color" if (propertyValue === undefined && propertyName === "lightColor") { - propertyValue = properties["color"] + propertyValue = selectedEntityProperties["color"] } - let isSubProperty = subProperties.indexOf(propertyName) > -1; + let isSubProperty = propertyData.subPropertyOf !== undefined; if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { continue; } - if (elProperty instanceof Array) { - if (elProperty[1].getAttribute("type") === "number") { // vectors - if (elProperty.length === 2 || elProperty.length === 3) { - // vec2/vec3 are array of 2/3 input numbers - elProperty[0].value = (propertyValue.x * 1 / elProperty[0].getAttribute("multiplier")).toFixed(4); - elProperty[1].value = (propertyValue.y * 1 / elProperty[1].getAttribute("multiplier")).toFixed(4); - if (elProperty[2] !== undefined) { - elProperty[2].value = (propertyValue.z * 1 / elProperty[2].getAttribute("multiplier")).toFixed(4); - } - } else if (elProperty.length === 4) { - // color is array of color picker and 3 input numbers - elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; - elProperty[1].value = propertyValue.red; - elProperty[2].value = propertyValue.green; - elProperty[3].value = propertyValue.blue; - } - } else if (elProperty[0].nodeName === "SPAN") { // icons - // icon is array of elSpan (icon glyph) and elLabel - elProperty[0].innerHTML = icons[propertyName][propertyValue]; - elProperty[0].style.display = "inline-block"; - elProperty[1].innerHTML = propertyValue; //+ " (" + data.selections.length + ")"; + switch (propertyData.type) { + case 'string': { + elProperty.value = propertyValue; + break; + } + case 'bool': { + let inverse = propertyData.inverse !== undefined ? propertyData.inverse : false; + if (isSubProperty) { + let propertyValue = selectedEntityProperties[propertyData.subPropertyOf]; + let subProperties = propertyValue.split(","); + let subPropertyValue = subProperties.indexOf(propertyName) > -1; + elProperty.checked = inverse ? !subPropertyValue : subPropertyValue; + } else { + elProperty.checked = inverse ? !propertyValue : propertyValue; + } + break; + } + case 'number': { + let fixedDecimals = propertyData.fixedDecimals !== undefined ? propertyData.fixedDecimals : 0; + elProperty.value = propertyValue.toFixed(fixedDecimals); + break; + } + case 'vec3': + case 'vec2': { + // vec2/vec3 are array of 2/3 elInput numbers + let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + elProperty[0].value = (propertyValue.x / multiplier).toFixed(4); + elProperty[1].value = (propertyValue.y / multiplier).toFixed(4); + if (elProperty[2] !== undefined) { + elProperty[2].value = (propertyValue.z / multiplier).toFixed(4); + } + break; + } + case 'color': { + // color is array of color picker and 3 elInput numbers + elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + + propertyValue.green + "," + + propertyValue.blue + ")"; + elProperty[1].value = propertyValue.red; + elProperty[2].value = propertyValue.green; + elProperty[3].value = propertyValue.blue; + break; + } + case 'dropdown': { + elProperty.value = propertyValue; + setDropdownText(elProperty); + break; + } + case 'textarea': { + elProperty.value = propertyValue; + setTextareaScrolling(elProperty); + break; + } + case 'icon': { + // icon is array of elSpan (icon glyph) and elLabel + elProperty[0].innerHTML = propertyData.icons[propertyValue]; + elProperty[0].style.display = "inline-block"; + elProperty[1].innerHTML = propertyValue; + break; } - } else if (elProperty.getAttribute("subPropertyOf")) { - let propertyValue = properties[elProperty.getAttribute("subPropertyOf")]; - let subProperties = propertyValue.split(","); - elProperty.checked = subProperties.indexOf(propertyName) > -1; - } else if (elProperty.getAttribute("type") === "number") { - let fixedDecimals = elProperty.getAttribute("fixedDecimals"); - elProperty.value = propertyValue.toFixed(fixedDecimals); - } else if (elProperty.getAttribute("type") === "checkbox") { - let inverse = elProperty.getAttribute("inverse") === "true"; - elProperty.checked = inverse ? !propertyValue : propertyValue; - } else if (elProperty.nodeName === "TEXTAREA") { - elProperty.value = propertyValue; - setTextareaScrolling(elProperty); - } else if (elProperty.nodeName === "DT") { // dropdown - elProperty.value = propertyValue; - setDropdownText(elProperty); - } else { - elProperty.value = propertyValue; } - let propertyShowRules = showPropertyRules[propertyName]; - if (propertyShowRules !== undefined) { - for (let propertyToShow in propertyShowRules) { - let showIfThisPropertyValue = propertyShowRules[propertyToShow]; + let showPropertyRules = property.showPropertyRules; + if (showPropertyRules !== undefined) { + for (let propertyToShow in showPropertyRules) { + let showIfThisPropertyValue = showPropertyRules[propertyToShow]; let show = String(propertyValue) === String(showIfThisPropertyValue); - let elPropertyToShow = elPropertyElements[propertyToShow]; - if (elPropertyToShow) { - let nodeToShow = elPropertyToShow; - if (elPropertyToShow.nodeName !== "DIV") { - let parentNode = elPropertyToShow.parentNode; - if (parentNode === undefined && elPropertyToShow instanceof Array) { - parentNode = elPropertyToShow[0].parentNode; - } - if (parentNode !== undefined) { - nodeToShow = parentNode; - } - } - nodeToShow.style.display = show ? "table" : "none"; - } + showPropertyElement(propertyToShow, show); } } } - let elGrabbable = elPropertyElements["grabbable"]; - let elTriggerable = elPropertyElements["triggerable"]; - let elIgnoreIK = elPropertyElements["ignoreIK"]; - elGrabbable.checked = elPropertyElements["dynamic"].checked; + let elGrabbable = getPropertyElement("grabbable"); + let elTriggerable = getPropertyElement("triggerable"); + let elIgnoreIK = getPropertyElement("ignoreIK"); + elGrabbable.checked = getPropertyElement("dynamic").checked; elTriggerable.checked = false; elIgnoreIK.checked = true; let grabbablesSet = false; let parsedUserData = {}; try { - parsedUserData = JSON.parse(properties.userData); + parsedUserData = JSON.parse(selectedEntityProperties.userData); if ("grabbableKey" in parsedUserData) { grabbablesSet = true; let grabbableData = parsedUserData.grabbableKey; @@ -2333,19 +2504,20 @@ function loaded() { elIgnoreIK.checked = true; } - if (properties.type === "Image") { - var imageLink = JSON.parse(properties.textures)["tex.picture"]; - elPropertyElements["image"].value = imageLink; - } else if (properties.type === "Material") { - let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; - let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; - let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; - if (properties.parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { - elParentMaterialNameString.value = properties.parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); + if (selectedEntityProperties.type === "Image") { + let imageLink = JSON.parse(selectedEntityProperties.textures)["tex.picture"]; + getPropertyElement("image").value = imageLink; + } else if (selectedEntityProperties.type === "Material") { + let elParentMaterialNameString = getPropertyElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyElement("selectSubmesh"); + let parentMaterialName = selectedEntityProperties.parentMaterialName; + if (parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { + elParentMaterialNameString.value = parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = false; } else { - elParentMaterialNameNumber.value = parseInt(properties.parentMaterialName); + elParentMaterialNameNumber.value = parseInt(parentMaterialName); showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); elParentMaterialNameCheckbox.checked = true; } @@ -2353,11 +2525,11 @@ function loaded() { let json = null; try { - json = JSON.parse(properties.userData); + json = JSON.parse(selectedEntityProperties.userData); } catch (e) { // normal text deleteJSONEditor(); - elPropertyElements["userData"].value = properties.userData; + getPropertyElement("userData").value = selectedEntityProperties.userData; showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); @@ -2376,11 +2548,11 @@ function loaded() { let materialJson = null; try { - materialJson = JSON.parse(properties.materialData); + materialJson = JSON.parse(selectedEntityProperties.materialData); } catch (e) { // normal text deleteJSONMaterialEditor(); - elPropertyElements["materialData"].value = properties.materialData; + getPropertyElement("materialData").value = selectedEntityProperties.materialData; showMaterialDataTextArea(); showNewJSONMaterialEditorButton(); hideSaveMaterialDataButton(); @@ -2397,16 +2569,16 @@ function loaded() { hideMaterialDataSaved(); } - if (properties.locked) { + if (selectedEntityProperties.locked) { disableProperties(); - elPropertyElements["locked"].removeAttribute('disabled'); + getPropertyElement("locked").removeAttribute('disabled'); } else { enableProperties(); disableSaveUserDataButton(); disableSaveMaterialDataButton() } - var activeElement = document.activeElement; + let activeElement = document.activeElement; if (doSelectElement && typeof activeElement.select !== "undefined") { activeElement.select(); } @@ -2416,14 +2588,15 @@ function loaded() { } // Server Script Status - let elServerScript = elPropertyElements["serverScripts"]; + let elServerScript = getPropertyElement("serverScripts"); + let serverScriptElementID = getPropertyElementID("serverScripts"); let elDiv = document.createElement('div'); elDiv.setAttribute("class", "property"); let elLabel = document.createElement('label'); - elLabel.setAttribute("for", "property-serverScripts-status"); + elLabel.setAttribute("for", serverScriptElementID + "-status"); elLabel.innerText = "Server Script Status"; let elServerScriptStatus = document.createElement('span'); - elServerScriptStatus.setAttribute("id", "property-serverScripts-status"); + elServerScriptStatus.setAttribute("id", serverScriptElementID + "-status"); elDiv.appendChild(elLabel); elDiv.appendChild(elServerScriptStatus); elServerScript.parentNode.appendChild(elDiv); @@ -2432,46 +2605,48 @@ function loaded() { elDiv = document.createElement('div'); elDiv.setAttribute("class", "property"); let elServerScriptError = document.createElement('textarea'); - elServerScriptError.setAttribute("id", "property-serverScripts-error"); + elServerScriptError.setAttribute("id", serverScriptElementID + "-error"); elDiv.appendChild(elServerScriptError); elServerScript.parentNode.appendChild(elDiv); - let elScript = elPropertyElements["script"]; + let elScript = getPropertyElement("script"); elScript.parentNode.setAttribute("class", "property url refresh"); elServerScript.parentNode.setAttribute("class", "property url refresh"); // User Data - let elUserData = elPropertyElements["userData"]; + let elUserData = getPropertyElement("userData"); + let userDataElementID = getPropertyElementID("userData"); elDiv = elUserData.parentNode; let elStaticUserData = document.createElement('div'); - elStaticUserData.setAttribute("id", "property-userData-static"); + elStaticUserData.setAttribute("id", userDataElementID + "-static"); let elUserDataEditor = document.createElement('div'); - elUserDataEditor.setAttribute("id", "property-userData-editor"); + elUserDataEditor.setAttribute("id", userDataElementID + "-editor"); let elUserDataSaved = document.createElement('span'); - elUserDataSaved.setAttribute("id", "property-userData-saved"); + elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); elUserDataSaved.innerText = "Saved!"; elDiv.childNodes[2].appendChild(elUserDataSaved); elDiv.insertBefore(elStaticUserData, elUserData); elDiv.insertBefore(elUserDataEditor, elUserData); // Material Data - let elMaterialData = elPropertyElements["materialData"]; + let elMaterialData = getPropertyElement("materialData"); + let materialDataElementID = getPropertyElementID("materialData"); elDiv = elMaterialData.parentNode; let elStaticMaterialData = document.createElement('div'); - elStaticMaterialData.setAttribute("id", "property-materialData-static"); + elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); let elMaterialDataEditor = document.createElement('div'); - elMaterialDataEditor.setAttribute("id", "property-materialData-editor"); + elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor"); let elMaterialDataSaved = document.createElement('span'); - elMaterialDataSaved.setAttribute("id", "property-materialData-saved"); + elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); elMaterialDataSaved.innerText = "Saved!"; elDiv.childNodes[2].appendChild(elMaterialDataSaved); elDiv.insertBefore(elStaticMaterialData, elMaterialData); elDiv.insertBefore(elMaterialDataEditor, elMaterialData); // User Data Fields - let elGrabbable = elPropertyElements["grabbable"]; - let elTriggerable = elPropertyElements["triggerable"]; - let elIgnoreIK = elPropertyElements["ignoreIK"]; + let elGrabbable = getPropertyElement("grabbable"); + let elTriggerable = getPropertyElement("triggerable"); + let elIgnoreIK = getPropertyElement("ignoreIK"); elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); }); @@ -2483,11 +2658,15 @@ function loaded() { }); // Special Property Callbacks - let elParentMaterialNameString = elPropertyElements["materialNameToReplace"]; - let elParentMaterialNameNumber = elPropertyElements["submeshToReplace"]; - let elParentMaterialNameCheckbox = elPropertyElements["selectSubmesh"]; - elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); - elParentMaterialNameNumber.addEventListener('change', function () { updateProperty("parentMaterialName", this.value); }); + let elParentMaterialNameString = getPropertyElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyElement("selectSubmesh"); + elParentMaterialNameString.addEventListener('change', function () { + updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); + }); + elParentMaterialNameNumber.addEventListener('change', function () { + updateProperty("parentMaterialName", this.value); + }); elParentMaterialNameCheckbox.addEventListener('change', function () { if (this.checked) { updateProperty("parentMaterialName", elParentMaterialNameNumber.value); @@ -2498,7 +2677,7 @@ function loaded() { } }); - elPropertyElements["image"].addEventListener('change', createImageURLUpdateFunction('textures')); + getPropertyElement("image").addEventListener('change', createImageURLUpdateFunction('textures')); // Collapsible sections let elCollapsible = document.getElementsByClassName("section-header"); @@ -2544,40 +2723,7 @@ function loaded() { //
    • ...
    • //
    //
    - //
    - function setDropdownText(dropdown) { - let lis = dropdown.parentNode.getElementsByTagName("li"); - let text = ""; - for (let i = 0; i < lis.length; i++) { - if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { - text = lis[i].textContent; - } - } - dropdown.firstChild.textContent = text; - } - - function toggleDropdown(event) { - let element = event.target; - if (element.nodeName !== "DT") { - element = element.parentNode; - } - element = element.parentNode; - let isDropped = element.getAttribute("dropped"); - element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); - } - - function setDropdownValue(event) { - let dt = event.target.parentNode.parentNode.previousSibling; - dt.value = event.target.getAttribute("value"); - dt.firstChild.textContent = event.target.textContent; - - dt.parentNode.setAttribute("dropped", "false"); - - let evt = document.createEvent("HTMLEvents"); - evt.initEvent("change", true, true); - dt.dispatchEvent(evt); - } - + //
let elDropdowns = document.getElementsByTagName("select"); for (let dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) { let options = elDropdowns[dropDownIndex].getElementsByTagName("option"); @@ -2623,7 +2769,7 @@ function loaded() { } let propertyName = elDropdowns[dropDownIndex].getAttribute("propertyName"); - elPropertyElements[propertyName] = dt; + properties[propertyName].el = dt; dt.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); } From ac0e999629031c696c96e28b3ff6d2a89ebf891f Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 10 Oct 2018 17:31:06 -0700 Subject: [PATCH 078/276] fix colors --- scripts/system/html/js/entityProperties.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 2841414d87..a58b5ca8cd 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -928,7 +928,9 @@ function getPropertyElement(propertyName) { } function getPropertyElementID(propertyName) { - return "property-" + propertyName; + let propertyElementID = "property-" + propertyName; + propertyElementID = propertyElementID.replace(".", "-"); + return propertyElementID; } function enableChildren(el, selector) { @@ -1077,7 +1079,6 @@ function showGroupsForType(type) { } - // PROPERTY UPDATE FUNCTIONS function updateProperty(propertyName, propertyValue) { @@ -1207,7 +1208,6 @@ function createImageURLUpdateFunction(propertyName) { } - // PROPERTY ELEMENT CREATION FUNCTIONS function createStringProperty(elProperty, elLabel, propertyData) { @@ -1381,7 +1381,7 @@ function createColorProperty(elProperty, elLabel, propertyData) { }, onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + emitColorPropertyUpdate(propertyName, rgb.r, rgb.g, rgb.b); } }); @@ -1573,7 +1573,6 @@ function addButtons(elProperty, propertyID, buttons, newRow) { } - // BUTTON CALLBACKS function rescaleDimensions() { @@ -1628,7 +1627,6 @@ function copySkyboxURLToAmbientURL() { } - // USER DATA FUNCTIONS function clearUserData() { @@ -1850,7 +1848,6 @@ function saveJSONUserData(noUpdate) { } - // MATERIAL DATA FUNCTIONS function clearMaterialData() { @@ -2035,7 +2032,6 @@ function bindAllNonJSONEditorElements() { } - // DROPDOWNS / TEXTAREAS / PARENT MATERIAL NAME FUNCTIONS function setDropdownText(dropdown) { @@ -2122,7 +2118,6 @@ function loaded() { let propertyType = property.type; let propertyName = property.propertyName; let propertyElementID = getPropertyElementID(propertyName); - propertyElementID = propertyElementID.replace(".", "-"); let elProperty; if (propertyType === "sub-header") { From a3a87ef48b1960b929eafef3adde9ae9c82f7af5 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 10 Oct 2018 17:52:26 -0700 Subject: [PATCH 079/276] Debug rendering of tracked objects, remove filters from body. --- interface/resources/controllers/vive.json | 29 ++++---------- interface/src/Application.cpp | 38 +++++++++++++++++++ .../filters/ExponentialSmoothingFilter.cpp | 2 +- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 8a7744efb3..42be6f3f04 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -51,30 +51,15 @@ { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, - { "from": "Vive.LeftHand", "to": "Standard.LeftHand"}, - { "from": "Vive.RightHand", "to": "Standard.RightHand"}, + { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Vive.RightHand", "to": "Standard.RightHand" }, - { - "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] - }, + { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot" }, + { "from": "Vive.RightFoot", "to" : "Standard.RightFoot" }, + { "from": "Vive.Hips", "to" : "Standard.Hips" }, + { "from": "Vive.Spine2", "to" : "Standard.Spine2" }, - { - "from": "Vive.RightFoot", "to" : "Standard.RightFoot", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] - }, - - { - "from": "Vive.Hips", "to" : "Standard.Hips", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] - }, - - { - "from": "Vive.Spine2", "to" : "Standard.Spine2", - "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}] - }, - - { "from": "Vive.Head", "to" : "Standard.Head"}, + { "from": "Vive.Head", "to" : "Standard.Head" }, { "from": "Vive.RightArm", "to" : "Standard.RightArm" }, { "from": "Vive.LeftArm", "to" : "Standard.LeftArm" }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index aa2b382c58..7457afe091 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5801,6 +5801,44 @@ void Application::update(float deltaTime) { controller::Pose pose = userInputMapper->getPoseState(action); myAvatar->setControllerPoseInSensorFrame(action, pose.transform(avatarToSensorMatrix)); } + + // AJT: TODO put a nice menu around this. + // Make sure to remove all markers when menu is turned off. + { + static const std::vector trackedObjectActions = { + controller::Action::TRACKED_OBJECT_00, + controller::Action::TRACKED_OBJECT_01, + controller::Action::TRACKED_OBJECT_02, + controller::Action::TRACKED_OBJECT_03, + controller::Action::TRACKED_OBJECT_04, + controller::Action::TRACKED_OBJECT_05, + controller::Action::TRACKED_OBJECT_06, + controller::Action::TRACKED_OBJECT_07, + controller::Action::TRACKED_OBJECT_08, + controller::Action::TRACKED_OBJECT_09, + controller::Action::TRACKED_OBJECT_10, + controller::Action::TRACKED_OBJECT_11, + controller::Action::TRACKED_OBJECT_12, + controller::Action::TRACKED_OBJECT_13, + controller::Action::TRACKED_OBJECT_14, + controller::Action::TRACKED_OBJECT_15 + }; + + int i = 0; + glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); + for (auto& action : trackedObjectActions) { + QString key = QString("_TrackedObject%1").arg(i); + controller::Pose pose = userInputMapper->getPoseState(action); + if (pose.valid) { + glm::vec3 pos = transformPoint(myAvatarMatrix, pose.translation); + glm::quat rot = glmExtractRotation(myAvatarMatrix) * pose.rotation; + DebugDraw::getInstance().addMarker(key, rot, pos, BLUE); + } else { + DebugDraw::getInstance().removeMarker(key); + } + i++; + } + } } updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... diff --git a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp index 9cf2673d55..0f204ce15f 100644 --- a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.cpp @@ -35,7 +35,7 @@ namespace controller { if (_prevSensorValue.isValid()) { // exponential smoothing filter sensorValue.translation = _translationConstant * sensorValue.getTranslation() + (1.0f - _translationConstant) * _prevSensorValue.getTranslation(); - sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), _rotationConstant); + sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), (1.0f - _rotationConstant)); // remember previous sensor space value. _prevSensorValue = sensorValue; From e5c2605ac29097349bd4fb1e1ce792736ab98b38 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Wed, 10 Oct 2018 17:53:14 -0700 Subject: [PATCH 080/276] Added filtered-puck-attach app to test filters on tracked objects. --- .../developer/tests/filtered-puck-attach.js | 425 ++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 scripts/developer/tests/filtered-puck-attach.js diff --git a/scripts/developer/tests/filtered-puck-attach.js b/scripts/developer/tests/filtered-puck-attach.js new file mode 100644 index 0000000000..23217886d5 --- /dev/null +++ b/scripts/developer/tests/filtered-puck-attach.js @@ -0,0 +1,425 @@ +// +// Created by Anthony J. Thibault on 2017/06/20 +// Modified by Robbie Uvanni to support multiple pucks and easier placement of pucks on entities, on 2017/08/01 +// Copyright 2017 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 +// +// When this script is running, a new app button, named "PUCKTACH", will be added to the toolbar/tablet. +// Click this app to bring up the puck attachment panel. +// + +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +/* global Xform */ +Script.include("/~/system/libraries/Xform.js"); + +(function() { // BEGIN LOCAL_SCOPE + +var TABLET_BUTTON_NAME = "PUCKATTACH"; +var TABLET_APP_URL = "https://s3.amazonaws.com/hifi-public/tony/html/filtered-puck-attach.html?2"; +var NUM_TRACKED_OBJECTS = 16; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-a.svg" +}); + +var shown = false; +function onScreenChanged(type, url) { + if (type === "Web" && url === TABLET_APP_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + shownChanged(true); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + shownChanged(false); + } + shown = false; + } +} +tablet.screenChanged.connect(onScreenChanged); + +function pad(num, size) { + var tempString = "000000000" + num; + return tempString.substr(tempString.length - size); +} +function indexToTrackedObjectName(index) { + return "TrackedObject" + pad(index, 2); +} +function getAvailableTrackedObjects() { + var available = []; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + var key = indexToTrackedObjectName(i); + var pose = Controller.getPoseValue(Controller.Standard[key]); + if (pose && pose.valid) { + available.push(i); + } + } + return available; +} +function sendAvailableTrackedObjects() { + tablet.emitScriptEvent(JSON.stringify({ + pucks: getAvailableTrackedObjects(), + selectedPuck: ((lastPuck === undefined) ? -1 : lastPuck.name) + })); +} + +function getRelativePosition(origin, rotation, offset) { + var relativeOffset = Vec3.multiplyQbyV(rotation, offset); + var worldPosition = Vec3.sum(origin, relativeOffset); + return worldPosition; +} +function getPropertyForEntity(entityID, propertyName) { + return Entities.getEntityProperties(entityID, [propertyName])[propertyName]; +} +function entityExists(entityID) { + return Object.keys(Entities.getEntityProperties(entityID)).length > 0; +} + +var VIVE_PUCK_MODEL = "https://s3.amazonaws.com/hifi-public/tony/vive_tracker_puck_y180z180.obj"; +var VIVE_PUCK_DIMENSIONS = { x: 0.0945, y: 0.0921, z: 0.0423 }; // 1/1000th scale of model +var VIVE_PUCK_SEARCH_DISTANCE = 1.5; // metres +var VIVE_PUCK_SPAWN_DISTANCE = 0.5; // metres +var VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE = 10.0; // metres +var VIVE_PUCK_NAME = "Tracked Puck"; + +var trackedPucks = { }; +var lastPuck; + +var DEFAULT_ROTATION_SMOOTHING_CONSTANT = 1.0; // no smoothing +var DEFAULT_TRANSLATION_SMOOTHING_CONSTANT = 1.0; // no smoothing +var DEFAULT_TRANSLATION_ACCELERATION_LIMIT = 1000; // only extreme accelerations are smoothed +var DEFAULT_ROTATION_ACCELERATION_LIMIT = 10000; // only extreme accelerations are smoothed + +function buildMappingJson() { + var obj = {name: "com.highfidelity.testing.filteredPuckAttach", channels: []}; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + obj.channels.push({ + from: "Vive." + indexToTrackedObjectName(i), + to: "Standard." + indexToTrackedObjectName(i), + filters: [ + { + type: "accelerationLimiter", + translationAccelerationLimit: DEFAULT_TRANSLATION_ACCELERATION_LIMIT, + rotationAccelerationLimit: DEFAULT_ROTATION_ACCELERATION_LIMIT + }, + { + type: "exponentialSmoothing", + translation: DEFAULT_TRANSLATION_SMOOTHING_CONSTANT, + rotation: DEFAULT_ROTATION_SMOOTHING_CONSTANT + } + ] + }); + } + return obj; +} + +var mappingJson = buildMappingJson(); + +var mapping; +function mappingChanged() { + if (mapping) { + mapping.disable(); + } + mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + mapping.enable(); +} + +function shownChanged(newShown) { + if (newShown) { + mappingChanged(); + } else { + mapping.disable(); + } +} + +function setTranslationAccelerationLimit(value) { + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].translationAccelerationLimit = value; + } + mappingChanged(); +} + +function setTranslationSnapThreshold(value) { + // TODO: convert from mm +} + +function setRotationAccelerationLimit(value) { + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].rotationAccelerationLimit = value; + } + mappingChanged(); +} + +function setRotationSnapThreshold(value) { + // TODO: convert from degrees +} + +function setTranslationSmoothingConstant(value) { + print("AJT: setting translation smoothing constant = " + value); + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[1].translation = value; + } + mappingChanged(); + print("AJT: done, value = " + value); +} + +function setRotationSmoothingConstant(value) { + print("AJT: setRotationSmoothingConstant =" + value); + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[1].rotation = value; + } + mappingChanged(); +} + + +function createPuck(puck) { + // create a puck entity and add it to our list of pucks + var action = indexToTrackedObjectName(puck.puckno); + var pose = Controller.getPoseValue(Controller.Standard[action]); + + if (pose && pose.valid) { + var spawnOffset = Vec3.multiply(Vec3.FRONT, VIVE_PUCK_SPAWN_DISTANCE); + var spawnPosition = getRelativePosition(MyAvatar.position, MyAvatar.orientation, spawnOffset); + + // should be an overlay + var puckEntityProperties = { + name: "Tracked Puck", + type: "Model", + modelURL: VIVE_PUCK_MODEL, + dimensions: VIVE_PUCK_DIMENSIONS, + position: spawnPosition, + userData: '{ "grabbableKey": { "grabbable": true, "kinematic": false } }' + }; + + var puckEntityID = Entities.addEntity(puckEntityProperties); + + // if we've already created this puck, destroy it + if (trackedPucks.hasOwnProperty(puck.puckno)) { + destroyPuck(puck.puckno); + } + // if we had an unfinalized puck, destroy it + if (lastPuck !== undefined) { + destroyPuck(lastPuck.name); + } + // create our new unfinalized puck + trackedPucks[puck.puckno] = { + puckEntityID: puckEntityID, + trackedEntityID: "" + }; + lastPuck = trackedPucks[puck.puckno]; + lastPuck.name = Number(puck.puckno); + } +} +function finalizePuck(puckName) { + // find nearest entity and change its parent to the puck + + if (!trackedPucks.hasOwnProperty(puckName)) { + print('2'); + return; + } + if (lastPuck === undefined) { + print('3'); + return; + } + if (lastPuck.name !== Number(puckName)) { + print('1'); + return; + } + + var puckPosition = getPropertyForEntity(lastPuck.puckEntityID, "position"); + var foundEntities = Entities.findEntities(puckPosition, VIVE_PUCK_SEARCH_DISTANCE); + + var foundEntity; + var leastDistance = Number.MAX_VALUE; + + for (var i = 0; i < foundEntities.length; i++) { + var entity = foundEntities[i]; + + if (getPropertyForEntity(entity, "name") !== VIVE_PUCK_NAME) { + var entityPosition = getPropertyForEntity(entity, "position"); + var d = Vec3.distance(entityPosition, puckPosition); + + if (d < leastDistance) { + leastDistance = d; + foundEntity = entity; + } + } + } + + if (foundEntity) { + lastPuck.trackedEntityID = foundEntity; + // remember the userdata and collisionless flag for the tracked entity since + // we're about to remove it and make it ungrabbable and collisionless + lastPuck.trackedEntityUserData = getPropertyForEntity(foundEntity, "userData"); + lastPuck.trackedEntityCollisionFlag = getPropertyForEntity(foundEntity, "collisionless"); + // update properties of the tracked entity + Entities.editEntity(lastPuck.trackedEntityID, { + "parentID": lastPuck.puckEntityID, + "userData": '{ "grabbableKey": { "grabbable": false } }', + "collisionless": 1 + }); + // remove reference to puck since it is now calibrated and finalized + lastPuck = undefined; + } +} +function updatePucks() { + // for each puck, update its position and orientation + for (var puckName in trackedPucks) { + if (!trackedPucks.hasOwnProperty(puckName)) { + continue; + } + var action = indexToTrackedObjectName(puckName); + var pose = Controller.getPoseValue(Controller.Standard[action]); + if (pose && pose.valid) { + var puck = trackedPucks[puckName]; + if (puck.trackedEntityID) { + if (entityExists(puck.trackedEntityID)) { + var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position); + var puckXform = new Xform(pose.rotation, pose.translation); + var finalXform = Xform.mul(avatarXform, puckXform); + + var d = Vec3.distance(MyAvatar.position, finalXform.pos); + if (d > VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE) { + print('tried to move tracked object too far away: ' + d); + return; + } + + Entities.editEntity(puck.puckEntityID, { + position: finalXform.pos, + rotation: finalXform.rot + }); + + // in case someone grabbed both entities and destroyed the + // child/parent relationship + Entities.editEntity(puck.trackedEntityID, { + parentID: puck.puckEntityID + }); + } else { + destroyPuck(puckName); + } + } + } + } +} +function destroyPuck(puckName) { + // unparent entity and delete its parent + if (!trackedPucks.hasOwnProperty(puckName)) { + return; + } + + var puck = trackedPucks[puckName]; + var puckEntityID = puck.puckEntityID; + var trackedEntityID = puck.trackedEntityID; + + // remove the puck as a parent entity and restore the tracked entities + // former userdata and collision flag + Entities.editEntity(trackedEntityID, { + "parentID": "{00000000-0000-0000-0000-000000000000}", + "userData": puck.trackedEntityUserData, + "collisionless": puck.trackedEntityCollisionFlag + }); + + delete trackedPucks[puckName]; + + // in some cases, the entity deletion may occur before the parent change + // has been processed, resulting in both the puck and the tracked entity + // to be deleted so we wait 100ms before deleting the puck, assuming + // that the parent change has occured + var DELETE_TIMEOUT = 100; // ms + Script.setTimeout(function() { + // delete the puck + Entities.deleteEntity(puckEntityID); + }, DELETE_TIMEOUT); +} +function destroyPucks() { + // remove all pucks and unparent entities + for (var puckName in trackedPucks) { + if (trackedPucks.hasOwnProperty(puckName)) { + destroyPuck(puckName); + } + } +} + +function onWebEventReceived(msg) { + var obj = {}; + + try { + obj = JSON.parse(msg); + } catch (err) { + return; + } + + print("AJT: onWebEventReceived = " + msg); + + switch (obj.cmd) { + case "ready": + sendAvailableTrackedObjects(); + break; + case "create": + createPuck(obj); + break; + case "finalize": + finalizePuck(obj.puckno); + break; + case "destroy": + destroyPuck(obj.puckno); + break; + case "translation-acceleration-limit": + setTranslationAccelerationLimit(Number(obj.val)); + break; + case "translation-snap-threshold": + setTranslationSnapThreshold(Number(obj.val)); + break; + case "rotation-acceleration-limit": + setRotationAccelerationLimit(Number(obj.val)); + break; + case "rotation-snap-threshold": + setRotationSnapThreshold(Number(obj.val)); + break; + case "translation-smoothing-constant": + setTranslationSmoothingConstant(Number(obj.val)); + break; + case "rotation-smoothing-constant": + setRotationSmoothingConstant(Number(obj.val)); + break; + } +} + +Script.update.connect(updatePucks); +Script.scriptEnding.connect(function () { + tablet.removeButton(tabletButton); + destroyPucks(); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); + if (mapping) { + mapping.disable(); + } +}); +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(TABLET_APP_URL); + } +}); +}()); // END LOCAL_SCOPE From 717a5ed31b0a2031cda2f4f9b032af6422dca0b7 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 11 Oct 2018 09:22:12 -0700 Subject: [PATCH 081/276] Added snap threshold to AccelerationLimiterFilter --- .../filters/AccelerationLimiterFilter.cpp | 23 +++++++++------ .../impl/filters/AccelerationLimiterFilter.h | 4 +-- .../developer/tests/filtered-puck-attach.js | 29 ++++++++++++++----- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp index aacbdd2cea..3db1a9fba6 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.cpp @@ -18,9 +18,9 @@ #include static const QString JSON_ROTATION_ACCELERATION_LIMIT = QStringLiteral("rotationAccelerationLimit"); -static const QString JSON_ROTATION_DECELERATION_LIMIT = QStringLiteral("rotationDecelerationLimit"); static const QString JSON_TRANSLATION_ACCELERATION_LIMIT = QStringLiteral("translationAccelerationLimit"); -static const QString JSON_TRANSLATION_DECELERATION_LIMIT = QStringLiteral("translationDecelerationLimit"); +static const QString JSON_TRANSLATION_SNAP_THRESHOLD = QStringLiteral("translationSnapThreshold"); +static const QString JSON_ROTATION_SNAP_THRESHOLD = QStringLiteral("rotationSnapThreshold"); static glm::vec3 angularVelFromDeltaRot(const glm::quat& deltaQ, float dt) { // Measure the angular velocity of a delta rotation quaternion by using quaternion logarithm. @@ -39,7 +39,7 @@ static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) { } static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3, - float dt, const float accLimit) { + float dt, float accLimit, float snapThreshold) { // measure the linear velocities of this step and the previoius step glm::vec3 v1 = (x3 - x1) / (2.0f * dt); @@ -52,7 +52,8 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con float aLen = glm::length(a); // pick limit based on if we are moving faster then our target - if (aLen > accLimit) { + float distToTarget = glm::length(x3 - x2); + if (aLen > accLimit && distToTarget > snapThreshold) { // Solve for a new `v1`, such that `a` does not exceed `aLimit` // This combines two steps: // 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`: @@ -71,7 +72,7 @@ static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, con } static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In, - float dt, const float accLimit) { + float dt, float accLimit, float snapThreshold) { // ensure quaternions have the same polarity glm::quat q0 = q0In; @@ -87,7 +88,8 @@ static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, co float aLen = glm::length(a); // clamp the acceleration if it is over the limit - if (aLen > accLimit) { + float angleToTarget = glm::angle(q3 * glm::inverse(q2)); + if (aLen > accLimit && angleToTarget > snapThreshold) { // solve for a new w1, such that a does not exceed the accLimit w1 = a * ((accLimit * dt) / aLen) + w0; @@ -120,10 +122,10 @@ namespace controller { glm::vec3 unfilteredTranslation = sensorValue.translation; sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation, - DELTA_TIME, _translationAccelerationLimit); + DELTA_TIME, _translationAccelerationLimit, _translationSnapThreshold); glm::quat unfilteredRot = sensorValue.rotation; sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation, - DELTA_TIME, _rotationAccelerationLimit); + DELTA_TIME, _rotationAccelerationLimit, _rotationSnapThreshold); // remember previous values. _prevPos[0] = _prevPos[1]; @@ -175,9 +177,12 @@ namespace controller { bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) { if (parameters.isObject()) { auto obj = parameters.toObject(); - if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT)) { + if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT) && + obj.contains(JSON_ROTATION_SNAP_THRESHOLD) && obj.contains(JSON_TRANSLATION_SNAP_THRESHOLD)) { _rotationAccelerationLimit = (float)obj[JSON_ROTATION_ACCELERATION_LIMIT].toDouble(); _translationAccelerationLimit = (float)obj[JSON_TRANSLATION_ACCELERATION_LIMIT].toDouble(); + _rotationSnapThreshold = (float)obj[JSON_ROTATION_SNAP_THRESHOLD].toDouble(); + _translationSnapThreshold = (float)obj[JSON_TRANSLATION_SNAP_THRESHOLD].toDouble(); return true; } } diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h index 06eeef1579..269fd54102 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -25,9 +25,9 @@ namespace controller { private: float _rotationAccelerationLimit { FLT_MAX }; - float _rotationDecelerationLimit { FLT_MAX }; float _translationAccelerationLimit { FLT_MAX }; - float _translationDecelerationLimit { FLT_MAX }; + float _rotationSnapThreshold { 0.0f }; + float _translationSnapThreshold { 0.0f }; mutable glm::vec3 _prevPos[3]; // sensor space mutable glm::quat _prevRot[3]; // sensor space diff --git a/scripts/developer/tests/filtered-puck-attach.js b/scripts/developer/tests/filtered-puck-attach.js index 23217886d5..ad9b17a0e4 100644 --- a/scripts/developer/tests/filtered-puck-attach.js +++ b/scripts/developer/tests/filtered-puck-attach.js @@ -101,6 +101,8 @@ var DEFAULT_ROTATION_SMOOTHING_CONSTANT = 1.0; // no smoothing var DEFAULT_TRANSLATION_SMOOTHING_CONSTANT = 1.0; // no smoothing var DEFAULT_TRANSLATION_ACCELERATION_LIMIT = 1000; // only extreme accelerations are smoothed var DEFAULT_ROTATION_ACCELERATION_LIMIT = 10000; // only extreme accelerations are smoothed +var DEFAULT_TRANSLATION_SNAP_THRESHOLD = 0; // no snapping +var DEFAULT_ROTATION_SNAP_THRESHOLD = 0; // no snapping function buildMappingJson() { var obj = {name: "com.highfidelity.testing.filteredPuckAttach", channels: []}; @@ -113,7 +115,9 @@ function buildMappingJson() { { type: "accelerationLimiter", translationAccelerationLimit: DEFAULT_TRANSLATION_ACCELERATION_LIMIT, - rotationAccelerationLimit: DEFAULT_ROTATION_ACCELERATION_LIMIT + rotationAccelerationLimit: DEFAULT_ROTATION_ACCELERATION_LIMIT, + translationSnapThreshold: DEFAULT_TRANSLATION_SNAP_THRESHOLD, + rotationSnapThreshold: DEFAULT_ROTATION_SNAP_THRESHOLD, }, { type: "exponentialSmoothing", @@ -154,7 +158,14 @@ function setTranslationAccelerationLimit(value) { } function setTranslationSnapThreshold(value) { - // TODO: convert from mm + // convert from mm + var MM_PER_M = 1000; + var meters = value / MM_PER_M; + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].translationSnapThreshold = meters; + } + mappingChanged(); } function setRotationAccelerationLimit(value) { @@ -166,21 +177,25 @@ function setRotationAccelerationLimit(value) { } function setRotationSnapThreshold(value) { - // TODO: convert from degrees + // convert from degrees + var PI_IN_DEGREES = 180; + var radians = value * (Math.pi / PI_IN_DEGREES); + var i; + for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { + mappingJson.channels[i].filters[0].translationSnapThreshold = radians; + } + mappingChanged(); } function setTranslationSmoothingConstant(value) { - print("AJT: setting translation smoothing constant = " + value); var i; for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { mappingJson.channels[i].filters[1].translation = value; } mappingChanged(); - print("AJT: done, value = " + value); } function setRotationSmoothingConstant(value) { - print("AJT: setRotationSmoothingConstant =" + value); var i; for (i = 0; i < NUM_TRACKED_OBJECTS; i++) { mappingJson.channels[i].filters[1].rotation = value; @@ -366,8 +381,6 @@ function onWebEventReceived(msg) { return; } - print("AJT: onWebEventReceived = " + msg); - switch (obj.cmd) { case "ready": sendAvailableTrackedObjects(); From 5faf26abd104b0db6d4539b5e074aee4f201bf82 Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 11 Oct 2018 10:38:59 -0700 Subject: [PATCH 082/276] turned off shoulder rotation when we don't have hmd lean recentering on. this temporarily fixes the behavior of the shoulders in this mode. todo: use the spine2 position to determine azimuth of hands, then they will work in all conditions --- interface/src/avatar/MyAvatar.cpp | 7 +++++++ interface/src/avatar/MySkeletonModel.cpp | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1f38971ab4..77c66d062f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -591,8 +591,15 @@ void MyAvatar::update(float deltaTime) { // draw hand azimuth vector glm::vec3 handAzimuthMidpoint = transformPoint(getTransform().getMatrix(), glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y)); DebugDraw::getInstance().drawRay(getWorldPosition(), handAzimuthMidpoint, glm::vec4(0.0f, 1.0f, 1.0f, 1.0f)); + + } + // temp: draw spine 2 position for hand azimuth purposes. + int spine2Index = getJointIndex("Spine2"); + glm::vec3 spine2WorldPosition = transformPoint(getTransform().getMatrix(), getAbsoluteJointTranslationInObjectFrame(spine2Index)); + DebugDraw::getInstance().addMarker("spine2 location", Quaternions::IDENTITY, spine2WorldPosition, glm::vec4(1)); + if (_goToPending) { setWorldPosition(_goToPosition); setWorldOrientation(_goToOrientation); diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 42ec582c47..1f97ce03f8 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -239,7 +239,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; // set spine2 if we have hand controllers - if (myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && + if (myAvatar->getHMDLeanRecenterEnabled() && myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { @@ -250,6 +250,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { bool headExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Head"), currentHeadPose); bool hipsExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Hips"), currentHipsPose); if (spine2Exists && headExists && hipsExists) { + // qCDebug(interfaceapp) << "hips forward direction "<< (currentHipsPose.rot() * glm::vec3(0.0f, 0.0f, 1.0f)); AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace()); glm::vec3 u, v, w; glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f); From 1adac78828403d0d26eb8e9ad7d0e8b76ee29b2c Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 11 Oct 2018 10:45:35 -0700 Subject: [PATCH 083/276] fix errors and set new anim parameters --- interface/src/avatar/AvatarManager.cpp | 19 +++++------- .../src/avatars-renderer/Avatar.cpp | 31 +++++++++++++------ .../src/avatars-renderer/Avatar.h | 5 ++- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1f2c9e462d..0bb1bc2e0a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -81,7 +81,7 @@ AvatarManager::AvatarManager(QObject* parent) : const float AVATAR_TRANSIT_TRIGGER_DISTANCE = 1.0f; const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing - const int AVATAR_TRANSIT_FRAMES_PER_METER = 1; // Based on testing + const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f; // Based on testing const QString START_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; const QString MIDDLE_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; @@ -92,9 +92,9 @@ AvatarManager::AvatarManager(QObject* parent) : _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; _transitConfig._isDistanceBased = true; - _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 14); + _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 10); _transitConfig._middleTransitAnimation = AvatarTransit::TransitAnimation(MIDDLE_ANIMATION_URL, 15, 0); - _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 16, 38); + _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 22, 49); } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { @@ -180,8 +180,9 @@ void AvatarManager::updateMyAvatar(float deltaTime) { PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); - bool sendFirstTransitPackage = (status == AvatarTransit::Status::START_TRANSIT); - bool blockTransitData = (status == AvatarTransit::Status::TRANSITING); + if (status != AvatarTransit::Status::IDLE) { + playTransitAnimations(status); + } _myAvatar->update(deltaTime); render::Transaction transaction; @@ -191,13 +192,9 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - - if (sendFirstTransitPackage || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused && !blockTransitData)) { + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server - PerformanceTimer perfTimer("send"); - if (sendFirstTransitPackage) { - _myAvatar->overrideNextPackagePositionData(_myAvatar->getTransit()->getEndPosition()); - } + PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index f154746707..6ac808ec66 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -144,9 +144,12 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const _totalDistance = glm::length(_transitLine); _easeType = config._easeType; const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - + _preTime = (float)config._startTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + _postTime = (float)config._endTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; - _totalTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; + _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; + _totalTime = _transitTime + _preTime + _postTime; _currentTime = 0.0f; _isTransiting = true; } @@ -173,19 +176,27 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { Status status = Status::IDLE; if (_isTransiting) { float nextTime = _currentTime + deltaTime; - glm::vec3 newPosition; - if (nextTime >= _totalTime) { - _currentPosition = _endPosition; - _isTransiting = false; - status = Status::END_TRANSIT; - } else { + if (nextTime < _preTime) { + _currentPosition = _startPosition; if (_currentTime == 0) { + status = Status::START_FRAME; + } + } else if (nextTime < _totalTime - _postTime){ + if (_currentTime <= _preTime) { status = Status::START_TRANSIT; } else { + float percentageIntoTransit = (nextTime - _preTime) / _transitTime; + _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; status = Status::TRANSITING; } - float percentageIntoTransit = nextTime / _totalTime; - _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; + } else { + _currentPosition = _endPosition; + if (nextTime >= _totalTime) { + _isTransiting = false; + status = Status::END_FRAME; + } else if (_currentTime < _totalTime - _postTime) { + status = Status::END_TRANSIT; + } } _currentTime = nextTime; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 21e359051f..ffe90ecaa2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -81,7 +81,7 @@ public: struct TransitConfig { TransitConfig() {}; int _totalFrames { 0 }; - int _framesPerMeter { 0 }; + float _framesPerMeter { 0.0f }; bool _isDistanceBased { false }; float _triggerDistance { 0 }; EaseType _easeType { EaseType::EASE_OUT }; @@ -115,7 +115,10 @@ private: glm::vec3 _transitLine; float _totalDistance { 0.0f }; + float _preTime { 0.0f }; float _totalTime { 0.0f }; + float _transitTime { 0.0f }; + float _postTime { 0.0f }; float _currentTime { 0.0f }; EaseType _easeType { EaseType::EASE_OUT }; Status _status { Status::IDLE }; From 2f10327ad36cc5bcd81d55efcb832e2e29a5765c Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 11 Oct 2018 10:57:39 -0700 Subject: [PATCH 084/276] Fix last anim value --- interface/src/avatar/AvatarManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 0bb1bc2e0a..87c0bc471b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -94,7 +94,7 @@ AvatarManager::AvatarManager(QObject* parent) : _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 10); _transitConfig._middleTransitAnimation = AvatarTransit::TransitAnimation(MIDDLE_ANIMATION_URL, 15, 0); - _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 22, 49); + _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 22, 27); } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { From 451142f9a3e15d555a7318a469298d88b1153355 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 11 Oct 2018 11:33:19 -0700 Subject: [PATCH 085/276] fixing some more interstittial issues --- scripts/system/controllers/controllerModules/teleport.js | 4 ++++ scripts/system/interstitialPage.js | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index bf5022cdaf..bccdd9007c 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -701,6 +701,10 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function(controllerData, deltaTime) { + if (Window.interstitialModeEnabled && !Window.isPhysicsEnabled()) { + return makeRunningValues(false, [], []); + } + var otherModule = this.getOtherModule(); if (!this.disabled && this.buttonValue !== 0 && !otherModule.active) { this.active = true; diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 19e603b4ab..040128ffcf 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -545,7 +545,11 @@ MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); MyAvatar.sessionUUIDChanged.connect(function() { var avatarSessionUUID = MyAvatar.sessionUUID; - Overlays.editOverlay(loadingSphereID, { parentID: avatarSessionUUID }); + Overlays.editOverlay(loadingSphereID, { + 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), + parentID: avatarSessionUUID + }); }); var toggle = true; From de1b6e717fc8f8acb46e61697e9c14431feed210 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 11 Oct 2018 11:35:56 -0700 Subject: [PATCH 086/276] Cancel animation on chained trasits --- interface/src/avatar/AvatarManager.cpp | 2 +- .../src/avatars-renderer/Avatar.cpp | 15 +++++++-------- libraries/avatars/src/AvatarData.cpp | 2 +- libraries/avatars/src/AvatarData.h | 1 + 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 87c0bc471b..80e4e4ba66 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -313,7 +313,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } - auto transitStatus = avatar->_transit.update(deltaTime, avatar->_globalPosition, _transitConfig); + auto transitStatus = avatar->_transit.update(deltaTime, avatar->_lastPosition, _transitConfig); if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) { avatar->_transit.reset(); avatar->setIsNewAvatar(false); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 6ac808ec66..53c2019138 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -114,19 +114,18 @@ void Avatar::setShowNamesAboveHeads(bool show) { } AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { - glm::vec3 currentPosition = _isTransiting ? _currentPosition : avatarPosition; - float oneFrameDistance = glm::length(currentPosition - _lastPosition); + float oneFrameDistance = _isTransiting ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); const float MAX_TRANSIT_DISTANCE = 30.0f; float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale; - if (oneFrameDistance > config._triggerDistance && !_isTransiting) { + if (oneFrameDistance > config._triggerDistance) { if (oneFrameDistance < scaledMaxTransitDistance) { - start(deltaTime, _lastPosition, currentPosition, config); + start(deltaTime, _lastPosition, avatarPosition, config); } else { - _lastPosition = currentPosition; - return Status::ABORT_TRANSIT; + _lastPosition = avatarPosition; + _status = Status::ABORT_TRANSIT; } } - _lastPosition = currentPosition; + _lastPosition = avatarPosition; _status = updatePosition(deltaTime); return _status; } @@ -150,7 +149,7 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; _totalTime = _transitTime + _preTime + _postTime; - _currentTime = 0.0f; + _currentTime = _isTransiting ? _preTime : 0.0f; _isTransiting = true; } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d78d83bc09..a22cc4a1d3 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -896,7 +896,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; + _lastPosition = _globalPosition = newValue; _globalPositionChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 46489451f7..dcdaa70ad7 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1378,6 +1378,7 @@ protected: // where Entities are located. This is currently only used by the mixer to decide how often to send // updates about one avatar to another. glm::vec3 _globalPosition { 0, 0, 0 }; + glm::vec3 _lastPosition { 0, 0, 0 }; glm::vec3 _globalPositionOverride { 0, 0, 0 }; bool _overrideGlobalPosition { false }; From 5528050898de75444b1caff4d6b3420beee488f8 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 11 Oct 2018 12:44:07 -0700 Subject: [PATCH 087/276] CR changes - propertyName vs propertyID, store name/elementID, return property element --- scripts/system/html/js/entityProperties.js | 527 ++++++++++----------- 1 file changed, 257 insertions(+), 270 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index a58b5ca8cd..fcd9251485 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -40,36 +40,35 @@ const GROUPS = [ label: NO_SELECTION, type: "icon", icons: ICON_FOR_TYPE, - defaultValue: "Box", - propertyName: "type", + propertyID: "type", }, { label: "Name", type: "string", - propertyName: "name", + propertyID: "name", }, { label: "ID", type: "string", - propertyName: "id", + propertyID: "id", readOnly: true, }, { label: "Parent", type: "string", - propertyName: "parentID", + propertyID: "parentID", }, { label: "Locked", glyph: "", type: "bool", - propertyName: "locked", + propertyID: "locked", }, { label: "Visible", glyph: "", type: "bool", - propertyName: "visible", + propertyID: "visible", }, ] }, @@ -84,12 +83,12 @@ const GROUPS = [ Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", Circle: "Circle", Quad: "Quad" }, - propertyName: "shape", + propertyID: "shape", }, { label: "Color", type: "color", - propertyName: "color", + propertyID: "color", }, ] }, @@ -100,17 +99,17 @@ const GROUPS = [ { label: "Text", type: "string", - propertyName: "text", + propertyID: "text", }, { label: "Text Color", type: "color", - propertyName: "textColor", + propertyID: "textColor", }, { label: "Background Color", type: "color", - propertyName: "backgroundColor", + propertyID: "backgroundColor", }, { label: "Line Height", @@ -119,12 +118,12 @@ const GROUPS = [ step: 0.005, fixedDecimals: 4, unit: "m", - propertyName: "lineHeight" + propertyID: "lineHeight" }, { label: "Face Camera", type: "bool", - propertyName: "faceCamera" + propertyID: "faceCamera" }, ] }, @@ -135,29 +134,29 @@ const GROUPS = [ { label: "Flying Allowed", type: "bool", - propertyName: "flyingAllowed", + propertyID: "flyingAllowed", }, { label: "Ghosting Allowed", type: "bool", - propertyName: "ghostingAllowed", + propertyID: "ghostingAllowed", }, { label: "Filter", type: "string", - propertyName: "filterURL", + propertyID: "filterURL", }, { label: "Key Light", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "keyLightMode", + propertyID: "keyLightMode", }, { label: "Key Light Color", type: "color", - propertyName: "keyLight.color", + propertyID: "keyLight.color", showPropertyRule: { "keyLightMode": "enabled" }, }, { @@ -167,7 +166,7 @@ const GROUPS = [ max: 10, step: 0.1, fixedDecimals: 2, - propertyName: "keyLight.intensity", + propertyID: "keyLight.intensity", showPropertyRule: { "keyLightMode": "enabled" }, }, { @@ -175,7 +174,7 @@ const GROUPS = [ type: "number", fixedDecimals: 2, unit: "deg", - propertyName: "keyLight.direction.y", + propertyID: "keyLight.direction.y", showPropertyRule: { "keyLightMode": "enabled" }, }, { @@ -183,45 +182,45 @@ const GROUPS = [ type: "number", fixedDecimals: 2, unit: "deg", - propertyName: "keyLight.direction.x", + propertyID: "keyLight.direction.x", showPropertyRule: { "keyLightMode": "enabled" }, }, { label: "Cast Shadows", type: "bool", - propertyName: "keyLight.castShadows", + propertyID: "keyLight.castShadows", showPropertyRule: { "keyLightMode": "enabled" }, }, { label: "Skybox", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "skyboxMode", + propertyID: "skyboxMode", }, { label: "Skybox Color", type: "color", - propertyName: "skybox.color", + propertyID: "skybox.color", showPropertyRule: { "skyboxMode": "enabled" }, }, { label: "Skybox URL", type: "string", - propertyName: "skybox.url", + propertyID: "skybox.url", showPropertyRule: { "skyboxMode": "enabled" }, }, { type: "buttons", buttons: [ { id: "copy", label: "Copy URL To Ambient", className: "black", onClick: copySkyboxURLToAmbientURL } ], - propertyName: "copyURLToAmbient", + propertyID: "copyURLToAmbient", showPropertyRule: { "skyboxMode": "enabled" }, }, { label: "Ambient Light", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "ambientLightMode", + propertyID: "ambientLightMode", }, { label: "Ambient Intensity", @@ -230,20 +229,20 @@ const GROUPS = [ max: 10, step: 0.1, fixedDecimals: 2, - propertyName: "ambientLight.ambientIntensity", + propertyID: "ambientLight.ambientIntensity", showPropertyRule: { "ambientLightMode": "enabled" }, }, { label: "Ambient URL", type: "string", - propertyName: "ambientLight.ambientURL", + propertyID: "ambientLight.ambientURL", showPropertyRule: { "ambientLightMode": "enabled" }, }, { label: "Haze", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "hazeMode", + propertyID: "hazeMode", }, { label: "Range", @@ -253,13 +252,13 @@ const GROUPS = [ step: 5, fixedDecimals: 0, unit: "m", - propertyName: "haze.hazeRange", + propertyID: "haze.hazeRange", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Use Altitude", type: "bool", - propertyName: "haze.hazeAltitudeEffect", + propertyID: "haze.hazeAltitudeEffect", showPropertyRule: { "hazeMode": "enabled" }, }, { @@ -270,7 +269,7 @@ const GROUPS = [ step: 10, fixedDecimals: 0, unit: "m", - propertyName: "haze.hazeBaseRef", + propertyID: "haze.hazeBaseRef", showPropertyRule: { "hazeMode": "enabled" }, }, { @@ -281,13 +280,13 @@ const GROUPS = [ step: 10, fixedDecimals: 0, unit: "m", - propertyName: "haze.hazeCeiling", + propertyID: "haze.hazeCeiling", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Haze Color", type: "color", - propertyName: "haze.hazeColor", + propertyID: "haze.hazeColor", showPropertyRule: { "hazeMode": "enabled" }, }, { @@ -297,19 +296,19 @@ const GROUPS = [ max: 1.0, step: 0.01, fixedDecimals: 2, - propertyName: "haze.hazeBackgroundBlend", + propertyID: "haze.hazeBackgroundBlend", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Enable Glare", type: "bool", - propertyName: "haze.hazeEnableGlare", + propertyID: "haze.hazeEnableGlare", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Glare Color", type: "color", - propertyName: "haze.hazeGlareColor", + propertyID: "haze.hazeGlareColor", showPropertyRule: { "hazeMode": "enabled" }, }, { @@ -319,14 +318,14 @@ const GROUPS = [ max: 180, step: 1, fixedDecimals: 0, - propertyName: "haze.hazeGlareAngle", + propertyID: "haze.hazeGlareAngle", showPropertyRule: { "hazeMode": "enabled" }, }, { label: "Bloom", type: "dropdown", options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyName: "bloomMode", + propertyID: "bloomMode", }, { label: "Bloom Intensity", @@ -335,7 +334,7 @@ const GROUPS = [ max: 1, step: 0.01, fixedDecimals: 2, - propertyName: "bloom.bloomIntensity", + propertyID: "bloom.bloomIntensity", showPropertyRule: { "bloomMode": "enabled" }, }, { @@ -345,7 +344,7 @@ const GROUPS = [ min: 1, step: 0.01, fixedDecimals: 2, - propertyName: "bloom.bloomThreshold", + propertyID: "bloom.bloomThreshold", showPropertyRule: { "bloomMode": "enabled" }, }, { @@ -355,7 +354,7 @@ const GROUPS = [ min: 2, step: 0.01, fixedDecimals: 2, - propertyName: "bloom.bloomSize", + propertyID: "bloom.bloomSize", showPropertyRule: { "bloomMode": "enabled" }, }, ] @@ -367,7 +366,7 @@ const GROUPS = [ { label: "Model", type: "string", - propertyName: "modelURL", + propertyID: "modelURL", }, { label: "Collision Shape", @@ -375,67 +374,67 @@ const GROUPS = [ options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , "static-mesh": "Exact - All polygons (non-dynamic only)" }, - propertyName: "shapeType", + propertyID: "shapeType", }, { label: "Compound Shape", type: "string", - propertyName: "compoundShapeURL", + propertyID: "compoundShapeURL", }, { label: "Animation", type: "string", - propertyName: "animation.url", + propertyID: "animation.url", }, { label: "Play Automatically", type: "bool", - propertyName: "animation.running", + propertyID: "animation.running", }, { label: "Allow Transition", type: "bool", - propertyName: "animation.allowTranslation", + propertyID: "animation.allowTranslation", }, { label: "Loop", type: "bool", - propertyName: "animation.loop", + propertyID: "animation.loop", }, { label: "Hold", type: "bool", - propertyName: "animation.hold", + propertyID: "animation.hold", }, { label: "Animation Frame", type: "number", - propertyName: "animation.currentFrame", + propertyID: "animation.currentFrame", }, { label: "First Frame", type: "number", - propertyName: "animation.firstFrame", + propertyID: "animation.firstFrame", }, { label: "Last Frame", type: "number", - propertyName: "animation.lastFrame", + propertyID: "animation.lastFrame", }, { label: "Animation FPS", type: "number", - propertyName: "animation.fps", + propertyID: "animation.fps", }, { label: "Texture", type: "textarea", - propertyName: "textures", + propertyID: "textures", }, { label: "Original Texture", type: "textarea", - propertyName: "originalTextures", + propertyID: "originalTextures", readOnly: true, }, ] @@ -447,7 +446,7 @@ const GROUPS = [ { label: "Image", type: "string", - propertyName: "image", + propertyID: "image", }, ] }, @@ -458,24 +457,26 @@ const GROUPS = [ { label: "Source", type: "string", - propertyName: "sourceUrl", + propertyID: "sourceUrl", }, { label: "Source Resolution", type: "number", - propertyName: "dpi", + propertyID: "dpi", }, ] }, { id: "light", addToGroup: "base", + addToGroup: "base", properties: [ { label: "Light Color", type: "color", - propertyName: "lightColor", // this actually shares "color" property with shape Color but + propertyID: "lightColor", // this actually shares "color" property with shape Color but // separating naming here to distinguish property element/data + propertyName: "color", }, { label: "Intensity", @@ -483,7 +484,7 @@ const GROUPS = [ min: 0, step: 0.1, fixedDecimals: 1, - propertyName: "intensity", + propertyID: "intensity", }, { label: "Fall-Off Radius", @@ -492,26 +493,26 @@ const GROUPS = [ step: 0.1, fixedDecimals: 1, unit: "m", - propertyName: "falloffRadius", + propertyID: "falloffRadius", }, { label: "Spotlight", type: "bool", - propertyName: "isSpotlight", + propertyID: "isSpotlight", }, { label: "Spotlight Exponent", type: "number", step: 0.01, fixedDecimals: 2, - propertyName: "exponent", + propertyID: "exponent", }, { label: "Spotlight Cut-Off", type: "number", step: 0.01, fixedDecimals: 2, - propertyName: "cutoff", + propertyID: "cutoff", }, ] }, @@ -522,7 +523,7 @@ const GROUPS = [ { label: "Material URL", type: "string", - propertyName: "materialURL", + propertyID: "materialURL", }, { label: "Material Data", @@ -530,30 +531,30 @@ const GROUPS = [ buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], - propertyName: "materialData", + propertyID: "materialData", }, { label: "Submesh to Replace", type: "number", min: 0, step: 1, - propertyName: "submeshToReplace", + propertyID: "submeshToReplace", }, { label: "Material Name to Replace", type: "string", - propertyName: "materialNameToReplace", + propertyID: "materialNameToReplace", }, { label: "Select Submesh", type: "bool", - propertyName: "selectSubmesh", + propertyID: "selectSubmesh", }, { label: "Priority", type: "number", min: 0, - propertyName: "priority", + propertyID: "priority", }, { label: "Material Position", @@ -563,7 +564,7 @@ const GROUPS = [ step: 0.1, vec2Type: "xy", subLabels: [ "x", "y" ], - propertyName: "materialMappingPos", + propertyID: "materialMappingPos", }, { label: "Material Scale", @@ -572,7 +573,7 @@ const GROUPS = [ step: 0.1, vec2Type: "wh", subLabels: [ "width", "height" ], - propertyName: "materialMappingScale", + propertyID: "materialMappingScale", }, { label: "Material Rotation", @@ -580,7 +581,7 @@ const GROUPS = [ step: 0.1, fixedDecimals: 2, unit: "deg", - propertyName: "materialMappingRot", + propertyID: "materialMappingRot", }, ] }, @@ -594,7 +595,7 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m", - propertyName: "position", + propertyID: "position", }, { label: "Rotation", @@ -603,7 +604,7 @@ const GROUPS = [ vec3Type: "pyr", subLabels: [ "pitch", "yaw", "roll" ], unit: "deg", - propertyName: "rotation", + propertyID: "rotation", }, { label: "Dimension", @@ -612,7 +613,7 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m", - propertyName: "dimensions", + propertyID: "dimensions", }, { label: "Scale", @@ -621,7 +622,7 @@ const GROUPS = [ unit: "%", buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], - propertyName: "scale", + propertyID: "scale", }, { label: "Pivot", @@ -630,14 +631,14 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "(ratio of dimension)", - propertyName: "registrationPoint", + propertyID: "registrationPoint", }, { label: "Align", type: "buttons", buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], - propertyName: "alignToGrid", + propertyID: "alignToGrid", }, ] }, @@ -650,33 +651,33 @@ const GROUPS = [ label: "Collides", type: "bool", inverse: true, - propertyName: "collisionless", + propertyID: "collisionless", column: -1, // before two columns div }, { label: "Dynamic", type: "bool", - propertyName: "dynamic", + propertyID: "dynamic", column: -1, // before two columns div }, { label: "Collides With", type: "sub-header", - propertyName: "collidesWithHeader", // not actually a property but used for naming/storing this element + propertyID: "collidesWithHeader", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, column: 1, }, { label: "", type: "sub-header", - propertyName: "collidesWithHeaderHelper", // not actually a property but used for naming/storing this element + propertyID: "collidesWithHeaderHelper", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, column: 2, }, { label: "Static Entities", type: "bool", - propertyName: "static", + propertyID: "static", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 1, @@ -684,7 +685,7 @@ const GROUPS = [ { label: "Dynamic Entities", type: "bool", - propertyName: "dynamic", + propertyID: "dynamic", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 2, @@ -692,7 +693,7 @@ const GROUPS = [ { label: "Kinematic Entities", type: "bool", - propertyName: "kinematic", + propertyID: "kinematic", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 1, @@ -700,7 +701,7 @@ const GROUPS = [ { label: "My Avatar", type: "bool", - propertyName: "myAvatar", + propertyID: "myAvatar", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 2, @@ -708,7 +709,7 @@ const GROUPS = [ { label: "Other Avatars", type: "bool", - propertyName: "otherAvatar", + propertyID: "otherAvatar", subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, column: 1, @@ -716,7 +717,7 @@ const GROUPS = [ { label: "Collision sound URL", type: "string", - propertyName: "collisionSoundURL", + propertyID: "collisionSoundURL", showPropertyRule: { "collisionless": "false" }, // having no column number means place this after two columns div }, @@ -730,78 +731,78 @@ const GROUPS = [ { label: "Grabbable", type: "bool", - propertyName: "grabbable", + propertyID: "grabbable", column: 1, }, { label: "Triggerable", type: "bool", - propertyName: "triggerable", + propertyID: "triggerable", column: 2, }, { label: "Cloneable", type: "bool", - propertyName: "cloneable", + propertyID: "cloneable", column: 1, }, { label: "Ignore inverse kinematics", type: "bool", - propertyName: "ignoreIK", + propertyID: "ignoreIK", column: 2, }, { label: "Clone Lifetime", type: "number", unit: "s", - propertyName: "cloneLifetime", + propertyID: "cloneLifetime", showPropertyRule: { "cloneable": "true" }, column: 1, }, { label: "Clone Limit", type: "number", - propertyName: "cloneLimit", + propertyID: "cloneLimit", showPropertyRule: { "cloneable": "true" }, column: 1, }, { label: "Clone Dynamic", type: "bool", - propertyName: "cloneDynamic", + propertyID: "cloneDynamic", showPropertyRule: { "cloneable": "true" }, column: 1, }, { label: "Clone Avatar Entity", type: "bool", - propertyName: "cloneAvatarEntity", + propertyID: "cloneAvatarEntity", showPropertyRule: { "cloneable": "true" }, column: 1, }, { // below properties having no column number means place them after two columns div label: "Can cast shadow", type: "bool", - propertyName: "canCastShadow", + propertyID: "canCastShadow", }, { label: "Script", type: "string", buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], - propertyName: "script", + propertyID: "script", }, { label: "Server Script", type: "string", buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], - propertyName: "serverScripts", + propertyID: "serverScripts", }, { label: "Lifetime", type: "number", unit: "s", - propertyName: "lifetime", + propertyID: "lifetime", }, { label: "User Data", @@ -809,7 +810,7 @@ const GROUPS = [ buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], - propertyName: "userData", + propertyID: "userData", }, ] }, @@ -823,13 +824,13 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m/s", - propertyName: "velocity", + propertyID: "velocity", }, { label: "Linear Damping", type: "number", fixedDecimals: 2, - propertyName: "damping", + propertyID: "damping", }, { label: "Angular Velocity", @@ -838,31 +839,31 @@ const GROUPS = [ vec3Type: "pyr", subLabels: [ "pitch", "yaw", "roll" ], unit: "deg/s", - propertyName: "angularVelocity", + propertyID: "angularVelocity", }, { label: "Angular Damping", type: "number", fixedDecimals: 4, - propertyName: "angularDamping", + propertyID: "angularDamping", }, { label: "Bounciness", type: "number", fixedDecimals: 4, - propertyName: "restitution", + propertyID: "restitution", }, { label: "Friction", type: "number", fixedDecimals: 4, - propertyName: "friction", + propertyID: "friction", }, { label: "Density", type: "number", fixedDecimals: 4, - propertyName: "density", + propertyID: "density", }, { label: "Gravity", @@ -870,7 +871,7 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m/s2", - propertyName: "gravity", + propertyID: "gravity", }, { label: "Acceleration", @@ -878,7 +879,7 @@ const GROUPS = [ vec3Type: "xyz", subLabels: [ "x", "y", "z" ], unit: "m/s2", - propertyName: "acceleration", + propertyID: "acceleration", }, ] }, @@ -923,14 +924,8 @@ function debugPrint(message) { // GENERAL PROPERTY/GROUP FUNCTIONS -function getPropertyElement(propertyName) { - return properties[propertyName].el; -} - -function getPropertyElementID(propertyName) { - let propertyElementID = "property-" + propertyName; - propertyElementID = propertyElementID.replace(".", "-"); - return propertyElementID; +function getPropertyElement(propertyID) { + return properties[propertyID].el; } function enableChildren(el, selector) { @@ -976,8 +971,8 @@ function disableProperties() { } } -function showPropertyElement(propertyName, show) { - let elProperty = properties[propertyName].el; +function showPropertyElement(propertyID, show) { + let elProperty = properties[propertyID].el; let elNode = elProperty; if (elNode.nodeName !== "DIV") { let elParent = elProperty.parentNode; @@ -992,9 +987,9 @@ function showPropertyElement(propertyName, show) { } function resetProperties() { - for (let propertyName in properties) { - let elProperty = properties[propertyName].el; - let propertyData = properties[propertyName].data; + for (let propertyID in properties) { + let elProperty = properties[propertyID].el; + let propertyData = properties[propertyID].data; switch (propertyData.type) { case 'string': { @@ -1043,16 +1038,13 @@ function resetProperties() { } case 'icon': { // icon is array of elSpan (icon glyph) and elLabel - if (propertyData.defaultValue !== undefined) { - elProperty[0].innerHTML = propertyData.icons[propertyData.defaultValue]; - elProperty[0].style.display = "inline-block"; - elProperty[1].innerHTML = propertyData.defaultValue; - } + elProperty[0].style.display = "none"; + elProperty[1].innerHTML = propertyData.label; break; } } - let showPropertyRules = properties[propertyName].showPropertyRules; + let showPropertyRules = properties[propertyID].showPropertyRules; if (showPropertyRules !== undefined) { for (let propertyToHide in showPropertyRules) { showPropertyElement(propertyToHide, false); @@ -1131,61 +1123,61 @@ function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { }; } -function createEmitVec2PropertyUpdateFunction(property, elX, elY) { +function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY) { return function () { let newValue = { x: elX.value, y: elY.value }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); }; } -function createEmitVec2PropertyUpdateFunctionWithMultiplier(property, elX, elY, multiplier) { +function createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elX, elY, multiplier) { return function () { let newValue = { x: elX.value * multiplier, y: elY.value * multiplier }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); }; } -function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { +function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ) { return function() { let newValue = { x: elX.value, y: elY.value, z: elZ ? elZ.value : 0 }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); }; } -function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) { +function createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elX, elY, elZ, multiplier) { return function() { let newValue = { x: elX.value * multiplier, y: elY.value * multiplier, z: elZ.value * multiplier }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); }; } -function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) { +function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue) { return function() { - emitColorPropertyUpdate(property, elRed.value, elGreen.value, elBlue.value); + emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value); }; } -function emitColorPropertyUpdate(property, red, green, blue) { +function emitColorPropertyUpdate(propertyName, red, green, blue) { let newValue = { red: red, green: green, blue: blue }; - updateProperty(property, newValue); + updateProperty(propertyName, newValue); } function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { @@ -1210,14 +1202,15 @@ function createImageURLUpdateFunction(propertyName) { // PROPERTY ELEMENT CREATION FUNCTIONS -function createStringProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createStringProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property text"); let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("id", elementID); elInput.setAttribute("type", "text"); if (propertyData.readOnly) { elInput.readOnly = true; @@ -1229,15 +1222,16 @@ function createStringProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elInput); if (propertyData.buttons !== undefined) { - addButtons(elProperty, propertyElementID, propertyData.buttons, false); + addButtons(elProperty, elementID, propertyData.buttons, false); } - properties[propertyName].el = elInput; + return elInput; } -function createBoolProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createBoolProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property checkbox"); @@ -1249,7 +1243,7 @@ function createBoolProperty(elProperty, elLabel, propertyData) { } let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("id", elementID); elInput.setAttribute("type", "checkbox"); elProperty.appendChild(elInput); @@ -1264,12 +1258,13 @@ function createBoolProperty(elProperty, elLabel, propertyData) { elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse)); } - properties[propertyName].el = elInput; + return elInput; } -function createVec3Property(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createVec3Property(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property " + propertyData.vec3Type + " fstuple"); @@ -1281,11 +1276,11 @@ function createVec3Property(elProperty, elLabel, propertyData) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputX = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[0], + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[0], propertyData.min, propertyData.max, propertyData.step); - let elInputY = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[1], + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], propertyData.min, propertyData.max, propertyData.step); - let elInputZ = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[2], + let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[2], propertyData.min, propertyData.max, propertyData.step); let inputChangeFunction; @@ -1299,12 +1294,13 @@ function createVec3Property(elProperty, elLabel, propertyData) { elInputY.addEventListener('change', inputChangeFunction); elInputZ.addEventListener('change', inputChangeFunction); - properties[propertyName].el = [ elInputX, elInputY, elInputZ ]; + return [ elInputX, elInputY, elInputZ ]; } -function createVec2Property(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createVec2Property(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property " + propertyData.vec2Type + " fstuple"); @@ -1316,9 +1312,9 @@ function createVec2Property(elProperty, elLabel, propertyData) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputX = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[0], + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[0], propertyData.min, propertyData.max, propertyData.step); - let elInputY = createTupleNumberInput(elTuple, propertyElementID, propertyData.subLabels[1], + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], propertyData.min, propertyData.max, propertyData.step); let inputChangeFunction; @@ -1331,18 +1327,18 @@ function createVec2Property(elProperty, elLabel, propertyData) { elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); - properties[propertyName].el = [elInputX, elInputY]; + return [elInputX, elInputY]; } -function createColorProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createColorProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; elProperty.setAttribute("class", "property rgb fstuple"); let elColorPicker = document.createElement('div'); elColorPicker.setAttribute("class", "color-picker"); - elColorPicker.setAttribute("id", propertyElementID); + elColorPicker.setAttribute("id", elementID); let elTuple = document.createElement('div'); elTuple.setAttribute("class", "tuple"); @@ -1351,16 +1347,16 @@ function createColorProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputR = createTupleNumberInput(elTuple, propertyElementID, "red", 0, 255, 1); - let elInputG = createTupleNumberInput(elTuple, propertyElementID, "green", 0, 255, 1); - let elInputB = createTupleNumberInput(elTuple, propertyElementID, "blue", 0, 255, 1); + let elInputR = createTupleNumberInput(elTuple, elementID, "red", 0, 255, 1); + let elInputG = createTupleNumberInput(elTuple, elementID, "green", 0, 255, 1); + let elInputB = createTupleNumberInput(elTuple, elementID, "blue", 0, 255, 1); let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); elInputR.addEventListener('change', inputChangeFunction); elInputG.addEventListener('change', inputChangeFunction); elInputB.addEventListener('change', inputChangeFunction); - let colorPickerID = "#" + propertyElementID; + let colorPickerID = "#" + elementID; colorPickers[colorPickerID] = $(colorPickerID).colpick({ colorScheme: 'dark', layout: 'hex', @@ -1385,18 +1381,19 @@ function createColorProperty(elProperty, elLabel, propertyData) { } }); - properties[propertyName].el = [elColorPicker, elInputR, elInputG, elInputB]; + return [elColorPicker, elInputR, elInputG, elInputB]; } -function createDropdownProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createDropdownProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property dropdown"); let elInput = document.createElement('select'); - elInput.setAttribute("id", propertyElementID); - elInput.setAttribute("propertyName", propertyName); + elInput.setAttribute("id", elementID); + elInput.setAttribute("propertyID", propertyData.propertyID); for (let optionKey in propertyData.options) { let option = document.createElement('option'); @@ -1410,33 +1407,24 @@ function createDropdownProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elLabel); elProperty.appendChild(elInput); - properties[propertyName].el = elInput; + return elInput; } -function createNumberProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createNumberProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property number"); addUnit(propertyData.unit, elLabel); let elInput = document.createElement('input'); - elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("id", elementID); elInput.setAttribute("type", "number"); - if (propertyData.min !== undefined) { - elInput.setAttribute("min", propertyData.min); - } - if (propertyData.max !== undefined) { - elInput.setAttribute("max", propertyData.max); - } - if (propertyData.step !== undefined) { - elInput.setAttribute("step", propertyData.step); - } let defaultValue = propertyData.defaultValue; if (defaultValue !== undefined) { - elInput.setAttribute("defaultValue", defaultValue); elInput.value = defaultValue; } @@ -1447,26 +1435,27 @@ function createNumberProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elInput); if (propertyData.buttons !== undefined) { - addButtons(elProperty, propertyElementID, propertyData.buttons, true); + addButtons(elProperty, elementID, propertyData.buttons, true); } - properties[propertyName].el = elInput; + return elInput; } -function createTextareaProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createTextareaProperty(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property textarea"); elProperty.appendChild(elLabel); if (propertyData.buttons !== undefined) { - addButtons(elProperty, propertyElementID, propertyData.buttons, true); + addButtons(elProperty, elementID, propertyData.buttons, true); } let elInput = document.createElement('textarea'); - elInput.setAttribute("id", propertyElementID); + elInput.setAttribute("id", elementID); if (propertyData.readOnly) { elInput.readOnly = true; } @@ -1475,30 +1464,30 @@ function createTextareaProperty(elProperty, elLabel, propertyData) { elProperty.appendChild(elInput); - properties[propertyName].el = elInput; + return elInput; } -function createIconProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createIconProperty(property, elProperty, elLabel) { + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property value"); - elLabel.setAttribute("id", propertyElementID); + elLabel.setAttribute("id", elementID); elLabel.innerHTML = " " + propertyData.label; let elSpan = document.createElement('span'); - elSpan.setAttribute("id", propertyElementID + "-icon"); + elSpan.setAttribute("id", elementID + "-icon"); elProperty.appendChild(elSpan); elProperty.appendChild(elLabel); - properties[propertyName].el = [ elSpan, elLabel ]; + return [ elSpan, elLabel ]; } -function createButtonsProperty(elProperty, elLabel, propertyData) { - let propertyName = propertyData.propertyName; - let propertyElementID = getPropertyElementID(propertyName); +function createButtonsProperty(property, elProperty, elLabel) { + let elementID = property.elementID; + let propertyData = property.data; elProperty.setAttribute("class", "property text"); @@ -1508,34 +1497,29 @@ function createButtonsProperty(elProperty, elLabel, propertyData) { } if (propertyData.buttons !== undefined) { - addButtons(elProperty, propertyElementID, propertyData.buttons, hasLabel); + addButtons(elProperty, elementID, propertyData.buttons, hasLabel); } - properties[propertyName].el = elProperty; + return elProperty; } -function createTupleNumberInput(elTuple, propertyID, subLabel, min, max, step) { - let elementPropertyID = propertyID + "-" + subLabel.toLowerCase(); +function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, step) { + let elementID = propertyElementID + "-" + subLabel.toLowerCase(); + let elDiv = document.createElement('div'); let elLabel = document.createElement('label'); elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1) + ":"; - elLabel.setAttribute("for", elementPropertyID); + elLabel.setAttribute("for", elementID); + let elInput = document.createElement('input'); - elInput.setAttribute("id", elementPropertyID); + elInput.setAttribute("id", elementID); elInput.setAttribute("type", "number"); elInput.setAttribute("class", subLabel); - if (min !== undefined) { - elInput.setAttribute("min", min); - } - if (max !== undefined) { - elInput.setAttribute("max", max); - } - if (step !== undefined) { - elInput.setAttribute("step", step); - } + elDiv.appendChild(elInput); elDiv.appendChild(elLabel); elTuple.appendChild(elDiv); + return elInput; } @@ -2114,23 +2098,24 @@ function loaded() { elGroup.appendChild(elLegend); } - group.properties.forEach(function(property) { - let propertyType = property.type; - let propertyName = property.propertyName; - let propertyElementID = getPropertyElementID(propertyName); + group.properties.forEach(function(propertyData) { + let propertyType = propertyData.type; + let propertyID = propertyData.propertyID; + let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID; + let propertyElementID = "property-" + propertyID; let elProperty; if (propertyType === "sub-header") { elProperty = document.createElement('legend'); - elProperty.innerText = property.label; + elProperty.innerText = propertyData.label; elProperty.setAttribute("class", "sub-section-header"); } else { elProperty = document.createElement('div'); elProperty.setAttribute("id", "div-" + propertyElementID); } - if (group.twoColumn && property.column !== undefined && property.column !== -1) { - let columnName = group.id + "column" + property.column; + if (group.twoColumn && propertyData.column !== undefined && propertyData.column !== -1) { + let columnName = group.id + "column" + propertyData.column; let elColumn = document.getElementById(columnName); if (!elColumn) { let columnDivName = group.id + "columnDiv"; @@ -2152,64 +2137,66 @@ function loaded() { } let elLabel = document.createElement('label'); - elLabel.innerText = property.label; + elLabel.innerText = propertyData.label; elLabel.setAttribute("for", propertyElementID); - properties[propertyName] = { data: property }; + properties[propertyID] = { data: propertyData, elementID: propertyElementID, name: propertyName }; + + let property = properties[propertyID]; switch (propertyType) { case 'string': { - createStringProperty(elProperty, elLabel, property); + properties[propertyID].el = createStringProperty(property, elProperty, elLabel); break; } case 'bool': { - createBoolProperty(elProperty, elLabel, property); + properties[propertyID].el = createBoolProperty(property, elProperty, elLabel); break; } case 'number': { - createNumberProperty(elProperty, elLabel, property); + properties[propertyID].el = createNumberProperty(property, elProperty, elLabel); break; } case 'vec3': { - createVec3Property(elProperty, elLabel, property); + properties[propertyID].el = createVec3Property(property, elProperty, elLabel); break; } case 'vec2': { - createVec2Property(elProperty, elLabel, property); + properties[propertyID].el = createVec2Property(property, elProperty, elLabel); break; } case 'color': { - createColorProperty(elProperty, elLabel, property); + properties[propertyID].el = createColorProperty(property, elProperty, elLabel); break; } case 'dropdown': { - createDropdownProperty(elProperty, elLabel, property); + properties[propertyID].el = createDropdownProperty(property, elProperty, elLabel); break; } case 'textarea': { - createTextareaProperty(elProperty, elLabel, property); + properties[propertyID].el = createTextareaProperty(property, elProperty, elLabel); break; } case 'icon': { - createIconProperty(elProperty, elLabel, property); + properties[propertyID].el = createIconProperty(property, elProperty, elLabel); break; } case 'buttons': { - createButtonsProperty(elProperty, elLabel, property); + properties[propertyID].el = createButtonsProperty(property, elProperty, elLabel); break; } case 'sub-header': { - properties[propertyName].el = elProperty; + properties[propertyID].el = elProperty; break; } default: { console.log("EntityProperties - Unknown property type " + - propertyType + " set to property " + propertyName); + propertyType + " set to property " + propertyID); break; } } - let showPropertyRule = property.showPropertyRule; + let showPropertyRule = propertyData.showPropertyRule; if (showPropertyRule !== undefined) { let dependentProperty = Object.keys(showPropertyRule)[0]; let dependentPropertyValue = showPropertyRule[dependentProperty]; @@ -2219,7 +2206,7 @@ function loaded() { if (properties[dependentProperty].showPropertyRules === undefined) { properties[dependentProperty].showPropertyRules = {}; } - properties[dependentProperty].showPropertyRules[propertyName] = dependentPropertyValue; + properties[dependentProperty].showPropertyRules[propertyID] = dependentPropertyValue; } }); @@ -2351,10 +2338,11 @@ function loaded() { elPropertiesList.className = selectedEntityProperties.type + 'Menu'; showGroupsForType(selectedEntityProperties.type); - for (let propertyName in properties) { - let property = properties[propertyName]; + for (let propertyID in properties) { + let property = properties[propertyID]; let elProperty = property.el; let propertyData = property.data; + let propertyName = property.name; // if this is a compound property name (i.e. animation.running) // then split it by . up to 3 times to find property value @@ -2376,11 +2364,6 @@ function loaded() { } else { propertyValue = selectedEntityProperties[propertyName]; } - - // workaround for shape Color & Light Color property fields sharing same property value "color" - if (propertyValue === undefined && propertyName === "lightColor") { - propertyValue = selectedEntityProperties["color"] - } let isSubProperty = propertyData.subPropertyOf !== undefined; if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { @@ -2397,7 +2380,7 @@ function loaded() { if (isSubProperty) { let propertyValue = selectedEntityProperties[propertyData.subPropertyOf]; let subProperties = propertyValue.split(","); - let subPropertyValue = subProperties.indexOf(propertyName) > -1; + let subPropertyValue = subProperties.indexOf(propertyID) > -1; elProperty.checked = inverse ? !subPropertyValue : subPropertyValue; } else { elProperty.checked = inverse ? !propertyValue : propertyValue; @@ -2583,8 +2566,9 @@ function loaded() { } // Server Script Status - let elServerScript = getPropertyElement("serverScripts"); - let serverScriptElementID = getPropertyElementID("serverScripts"); + let serverScriptProperty = properties["serverScripts"]; + let elServerScript = serverScriptProperty.el; + let serverScriptElementID = serverScriptProperty.elementID; let elDiv = document.createElement('div'); elDiv.setAttribute("class", "property"); let elLabel = document.createElement('label'); @@ -2609,8 +2593,9 @@ function loaded() { elServerScript.parentNode.setAttribute("class", "property url refresh"); // User Data - let elUserData = getPropertyElement("userData"); - let userDataElementID = getPropertyElementID("userData"); + let userDataProperty = properties["userData"]; + let elUserData = userDataProperty.el; + let userDataElementID = userDataProperty.elementID; elDiv = elUserData.parentNode; let elStaticUserData = document.createElement('div'); elStaticUserData.setAttribute("id", userDataElementID + "-static"); @@ -2624,8 +2609,9 @@ function loaded() { elDiv.insertBefore(elUserDataEditor, elUserData); // Material Data - let elMaterialData = getPropertyElement("materialData"); - let materialDataElementID = getPropertyElementID("materialData"); + let materialDataProperty = properties["materialData"]; + let elMaterialData = materialDataProperty.el; + let materialDataElementID = materialDataProperty.elementID; elDiv = elMaterialData.parentNode; let elStaticMaterialData = document.createElement('div'); elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); @@ -2763,9 +2749,10 @@ function loaded() { ul.appendChild(li); } - let propertyName = elDropdowns[dropDownIndex].getAttribute("propertyName"); - properties[propertyName].el = dt; - dt.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + let propertyID = elDropdowns[dropDownIndex].getAttribute("propertyID"); + let property = properties[propertyID]; + property.el = dt; + dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name)); } elDropdowns = document.getElementsByTagName("select"); From 3f08400741d834df27198d79b5cf9f13e2b0d14f Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 11 Oct 2018 12:48:08 -0700 Subject: [PATCH 088/276] comment --- scripts/system/html/js/entityProperties.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index fcd9251485..3ff24d013c 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -474,9 +474,8 @@ const GROUPS = [ { label: "Light Color", type: "color", - propertyID: "lightColor", // this actually shares "color" property with shape Color but - // separating naming here to distinguish property element/data - propertyName: "color", + propertyID: "lightColor", + propertyName: "color", // actual entity property name }, { label: "Intensity", From 62c5fb8e2e34fa53b47814d89360ab5a117ae364 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 11 Oct 2018 14:25:06 -0700 Subject: [PATCH 089/276] tweaks / dropdown fix --- scripts/system/html/js/entityProperties.js | 32 ++++++++++------------ 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 3ff24d013c..1d8a397f07 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1383,7 +1383,7 @@ function createColorProperty(property, elProperty, elLabel) { return [elColorPicker, elInputR, elInputG, elInputB]; } -function createDropdownProperty(property, elProperty, elLabel) { +function createDropdownProperty(property, propertyID, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; @@ -1392,7 +1392,7 @@ function createDropdownProperty(property, elProperty, elLabel) { let elInput = document.createElement('select'); elInput.setAttribute("id", elementID); - elInput.setAttribute("propertyID", propertyData.propertyID); + elInput.setAttribute("propertyID", propertyID); for (let optionKey in propertyData.options) { let option = document.createElement('option'); @@ -2015,7 +2015,7 @@ function bindAllNonJSONEditorElements() { } -// DROPDOWNS / TEXTAREAS / PARENT MATERIAL NAME FUNCTIONS +// DROPDOWN FUNCTIONS function setDropdownText(dropdown) { let lis = dropdown.parentNode.getElementsByTagName("li"); @@ -2050,6 +2050,9 @@ function setDropdownValue(event) { dt.dispatchEvent(evt); } + +// TEXTAREA / PARENT MATERIAL NAME FUNCTIONS + function setTextareaScrolling(element) { var isScrolling = element.scrollHeight > element.offsetHeight; element.setAttribute("scrolling", isScrolling ? "true" : "false"); @@ -2169,7 +2172,7 @@ function loaded() { break; } case 'dropdown': { - properties[propertyID].el = createDropdownProperty(property, elProperty, elLabel); + properties[propertyID].el = createDropdownProperty(property, propertyID, elProperty, elLabel); break; } case 'textarea': { @@ -2252,10 +2255,6 @@ function loaded() { } resetProperties(); - - getPropertyElement("type")[0].style.display = "none"; - getPropertyElement("type")[1].innerHTML = NO_SELECTION; - elPropertiesList.className = ''; showGroupsForType("None"); deleteJSONEditor(); @@ -2297,17 +2296,20 @@ function loaded() { } resetProperties(); - - getPropertyElement("type")[0].innerHTML = ICON_FOR_TYPE[type]; - getPropertyElement("type")[0].style.display = "inline-block"; - getPropertyElement("type")[1].innerHTML = type + " (" + data.selections.length + ")"; - elPropertiesList.className = ''; showGroupsForType(type); + let typeProperty = properties["type"]; + let elTypeProperty = typeProperty.el; + elTypeProperty[0].innerHTML = typeProperty.data.icons[type]; + elTypeProperty[0].style.display = "inline-block"; + elTypeProperty[1].innerHTML = type + " (" + data.selections.length + ")"; + disableProperties(); } else { selectedEntityProperties = data.selections[0].properties; + showGroupsForType(selectedEntityProperties.type); + if (lastEntityID !== '"' + selectedEntityProperties.id + '"' && lastEntityID !== null) { if (editor !== null) { saveUserData(); @@ -2333,10 +2335,6 @@ function loaded() { } } - // Create class name for css ruleset filtering - elPropertiesList.className = selectedEntityProperties.type + 'Menu'; - showGroupsForType(selectedEntityProperties.type); - for (let propertyID in properties) { let property = properties[propertyID]; let elProperty = property.el; From f73d974ad77b62a41b4d540e60b1cc1c584db98f Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 11 Oct 2018 15:14:04 -0700 Subject: [PATCH 090/276] added a lock state so you can lock sit or stand state, also made the hand azimuth relative to the spine2 location in avatar space. This stops the arms from behaving badly when the hands are in front of the chest but behind the root position of the avatar --- interface/src/avatar/MyAvatar.cpp | 119 ++++++++++++++--------- interface/src/avatar/MyAvatar.h | 1 + interface/src/avatar/MySkeletonModel.cpp | 3 +- 3 files changed, 75 insertions(+), 48 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 77c66d062f..6b4459e97c 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -531,52 +531,57 @@ void MyAvatar::update(float deltaTime) { glm::vec3 sensorHips = transformPoint(glm::inverse(getSensorToWorldMatrix()), worldHips); // put update sit stand state counts here - if (getIsInSittingState()) { - if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we recenter upwards then no longer in sitting state - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; + if (!_lockSitStandState) { + if (getIsInSittingState()) { + if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we recenter upwards then no longer in sitting state + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setResetMode(true); + setIsInSittingState(false); + setCenterOfGravityModelEnabled(true); + setSitStandStateChange(true); } - _averageUserHeightCount = 1; - setResetMode(true); - setIsInSittingState(false); - setCenterOfGravityModelEnabled(true); - setSitStandStateChange(true); + qCDebug(interfaceapp) << "going to stand state"; + } else { + _sitStandStateCount = 0; + // tipping point is average height when sitting. + _tippingPoint = averageSensorSpaceHeight; } } else { - _sitStandStateCount = 0; - // tipping point is average height when sitting. - _tippingPoint = averageSensorSpaceHeight; - } - } else { - // in the standing state - if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint))) {// && (angleSpine2 > COSINE_TEN_DEGREES) && !(sensorHips.y > (0.4f * averageSensorSpaceHeight))) { - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; - } - _averageUserHeightCount = 1; - setResetMode(true); - setIsInSittingState(true); - setCenterOfGravityModelEnabled(false); - setSitStandStateChange(true); - } - } else { - // returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); - // use the mode height for the tipping point when we are standing. - _tippingPoint = getCurrentStandingHeight(); - _sitStandStateCount = 0; - } + // in the standing state + if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint))) {// && (angleSpine2 > COSINE_TEN_DEGREES) && !(sensorHips.y > (0.4f * averageSensorSpaceHeight))) { + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setResetMode(true); + setIsInSittingState(true); + setCenterOfGravityModelEnabled(false); + setSitStandStateChange(true); + } + qCDebug(interfaceapp) << "going to sit state"; + } else { + // returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + // use the mode height for the tipping point when we are standing. + _tippingPoint = getCurrentStandingHeight(); + _sitStandStateCount = 0; + } + + } } @@ -939,6 +944,15 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { // Find the vector halfway between the hip to hand azimuth vectors // This midpoint hand azimuth is in Avatar space glm::vec2 MyAvatar::computeHandAzimuth() const { + int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); + glm::vec3 azimuthOrigin(0.0f,0.0f,0.0f); + if (!(spine2Index < 0)) { + // use the spine for the azimuth origin. + azimuthOrigin = getAbsoluteJointTranslationInObjectFrame(spine2Index); + } else { + // use the avatar root as the azimuth origin. + } + controller::Pose leftHandPoseAvatarSpace = getLeftHandPose(); controller::Pose rightHandPoseAvatarSpace = getRightHandPose(); controller::Pose headPoseAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD); @@ -946,11 +960,13 @@ glm::vec2 MyAvatar::computeHandAzimuth() const { glm::vec2 latestHipToHandController = _hipToHandController; if (leftHandPoseAvatarSpace.isValid() && rightHandPoseAvatarSpace.isValid() && headPoseAvatarSpace.isValid()) { + glm::vec3 rightHandOffset = rightHandPoseAvatarSpace.translation - azimuthOrigin; + glm::vec3 leftHandOffset = leftHandPoseAvatarSpace.translation - azimuthOrigin; // we need the old azimuth reading to prevent flipping the facing direction 180 // in the case where the hands go from being slightly less than 180 apart to slightly more than 180 apart. glm::vec2 oldAzimuthReading = _hipToHandController; - if ((glm::length(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)) > 0.0f) && (glm::length(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)) > 0.0f)) { - latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)), glm::normalize(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)), HALFWAY); + if ((glm::length(glm::vec2(rightHandOffset.x, rightHandOffset.z)) > 0.0f) && (glm::length(glm::vec2(leftHandOffset.x, leftHandOffset.z)) > 0.0f)) { + latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandOffset.x, rightHandOffset.z)), glm::normalize(glm::vec2(leftHandOffset.x, leftHandOffset.z)), HALFWAY); } else { latestHipToHandController = glm::vec2(0.0f, -1.0f); } @@ -4134,21 +4150,29 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl const int SQUATTY_COUNT_THRESHOLD = 600; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); + bool returnValue = false; + returnValue = (offset.y > CYLINDER_TOP);// || (offset.y < CYLINDER_BOTTOM); + if (myAvatar.getSitStandStateChange()) { + qCDebug(interfaceapp) << "sit state change"; returnValue = true; } if (myAvatar.getIsInSittingState()) { if (offset.y < SITTING_BOTTOM) { // we recenter when sitting. + qCDebug(interfaceapp) << "lean back sitting "; returnValue = true; } } else { // in the standing state - returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); if (myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) { myAvatar._squatCount = 0; - returnValue = true; + qCDebug(interfaceapp) << "squatting "; + // returnValue = true; + } + if (returnValue == true) { + qCDebug(interfaceapp) << "above or below capsule in standing"; } } return returnValue; @@ -4182,7 +4206,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } } - + if (_velocityCount > 60) { if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); @@ -4192,6 +4216,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat _velocityCount++; } } + } else { if (!isActive(Rotation) && getForceActivateRotation()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f268a05a15..f026f39493 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1824,6 +1824,7 @@ private: float _sumUserHeightSensorSpace{ DEFAULT_AVATAR_HEIGHT }; int _averageUserHeightCount{ 1 }; bool _sitStandStateChange{ false }; + bool _lockSitStandState { true }; // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 1f97ce03f8..ce8fefa0c5 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -239,7 +239,8 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; // set spine2 if we have hand controllers - if (myAvatar->getHMDLeanRecenterEnabled() && myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && + // myAvatar->getHMDLeanRecenterEnabled() && + if (myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { From 1bcbda7ad6454988e4fba25c2e1f389109ba701d Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 11 Oct 2018 16:16:24 -0700 Subject: [PATCH 091/276] Prevent race on internal client traits members --- assignment-client/src/Agent.h | 1 - interface/src/avatar/MyAvatar.cpp | 1 + interface/src/avatar/MyAvatar.h | 1 - libraries/avatars/src/ClientTraitsHandler.cpp | 23 +++++++++++++++++++ libraries/avatars/src/ClientTraitsHandler.h | 19 +++++++-------- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 2b5ff51b49..7d47c8e713 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -21,7 +21,6 @@ #include #include -#include #include #include #include diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b347963cf1..5418410dd4 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 16b765711a..c99fd3bce6 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index a06b53da7c..f8247d9e52 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -31,7 +31,27 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) : nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride"); } +void ClientTraitsHandler::markTraitUpdated(AvatarTraits::TraitType updatedTrait) { + Lock lock(_traitLock); + _traitStatuses[updatedTrait] = Updated; + _hasChangedTraits = true; +} + +void ClientTraitsHandler::markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID) { + Lock lock(_traitLock); + _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); + _hasChangedTraits = true; +} + +void ClientTraitsHandler::markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID) { + Lock lock(_traitLock); + _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); + _hasChangedTraits = true; +} + void ClientTraitsHandler::resetForNewMixer() { + Lock lock(_traitLock); + // re-set the current version to 0 _currentTraitVersion = AvatarTraits::DEFAULT_TRAIT_VERSION; @@ -46,6 +66,8 @@ void ClientTraitsHandler::resetForNewMixer() { } void ClientTraitsHandler::sendChangedTraitsToMixer() { + Lock lock(_traitLock); + if (hasChangedTraits() || _shouldPerformInitialSend) { // we have at least one changed trait to send @@ -113,6 +135,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { void ClientTraitsHandler::processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode) { if (sendingNode->getType() == NodeType::AvatarMixer) { + Lock lock(_traitLock); while (message->getBytesLeftToRead()) { AvatarTraits::TraitType traitType; message->readPrimitive(&traitType); diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h index 27ba58d46b..3900268101 100644 --- a/libraries/avatars/src/ClientTraitsHandler.h +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -26,14 +26,11 @@ public: void sendChangedTraitsToMixer(); - bool hasChangedTraits() { return _hasChangedTraits; } + bool hasChangedTraits() const { return _hasChangedTraits; } - void markTraitUpdated(AvatarTraits::TraitType updatedTrait) - { _traitStatuses[updatedTrait] = Updated; _hasChangedTraits = true; } - void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID) - { _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); _hasChangedTraits = true; } - void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID) - { _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); _hasChangedTraits = true; } + void markTraitUpdated(AvatarTraits::TraitType updatedTrait); + void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID); + void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID); void resetForNewMixer(); @@ -41,17 +38,21 @@ public slots: void processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode); private: + using Mutex = std::recursive_mutex; + using Lock = std::lock_guard; + enum ClientTraitStatus { Unchanged, Updated, Deleted }; - AvatarData* _owningAvatar; + AvatarData* const _owningAvatar; + Mutex _traitLock; AvatarTraits::AssociatedTraitValues _traitStatuses; - AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION }; + AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION }; AvatarTraits::TraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION }; bool _shouldPerformInitialSend { false }; From a6339bbe859a00a0406b83be62d5b09522f8a3fa Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Thu, 11 Oct 2018 17:26:05 -0700 Subject: [PATCH 092/276] bug fix and new menu 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 12e2b7a4c6..8bb49f0973 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-RC73.zip - URL_MD5 0c5edfb63cafb042311d3cf25261fbf2 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC75.zip + URL_MD5 b4225d058952e17976ac228330ce8d51 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From 67afc862224dcb753acdf62b4a8d77f276806b8c Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 11 Oct 2018 18:39:44 -0700 Subject: [PATCH 093/276] cleanup --- interface/resources/qml/hifi/AvatarApp.qml | 1 + .../resources/qml/hifi/avatarapp/Settings.qml | 19 ++++++++++++ interface/src/avatar/MyAvatar.cpp | 29 +++++++++---------- interface/src/avatar/MyAvatar.h | 13 ++++++++- interface/src/avatar/MySkeletonModel.cpp | 3 +- scripts/system/avatarapp.js | 14 ++++++++- 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index b06a2ca67c..bf647b65bb 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -253,6 +253,7 @@ Rectangle { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', collisionsEnabled : settings.avatarCollisionsOn, sittingEnabled : settings.avatarSittingOn, + lockStateEnabled : settings.avatarLockSitStandStateOn, animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, collisionSoundUrl : settings.avatarCollisionSoundUrl }; diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index c4289ca650..8749079940 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -21,6 +21,7 @@ Rectangle { property alias dominantHandIsLeft: leftHandRadioButton.checked property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked property alias avatarSittingOn: sitRadiobutton.checked + property alias avatarLockSitStandStateOn: lockSitStandStateCheckbox.checked property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text @@ -52,6 +53,12 @@ Rectangle { standRadioButton.checked = true; } + if (settings.lockStateEnabled) { + lockSitStandStateCheckbox.checked = true; + } else { + lockSitStandStateCheckbox.checked = false; + } + avatarAnimationJSON = settings.animGraphUrl; avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; avatarCollisionSoundUrl = settings.collisionSoundUrl; @@ -343,6 +350,18 @@ Rectangle { text: "Stand" boxSize: 20 } + + // "Lock State" Checkbox + + HifiControlsUit.CheckBox { + id: lockSitStandStateCheckbox; + visible: activeTab == "nearbyTab"; + anchors.right: reloadNearbyContainer.left; + anchors.rightMargin: 20; + checked: settings.lockStateEnabled; + text: "lock"; + boxSize: 24; + } } ColumnLayout { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6b4459e97c..745705f7b8 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -531,7 +531,7 @@ void MyAvatar::update(float deltaTime) { glm::vec3 sensorHips = transformPoint(glm::inverse(getSensorToWorldMatrix()), worldHips); // put update sit stand state counts here - if (!_lockSitStandState) { + if (getIsSitStandStateLocked()) { if (getIsInSittingState()) { if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { // if we recenter upwards then no longer in sitting state @@ -584,7 +584,6 @@ void MyAvatar::update(float deltaTime) { } } - if (_drawAverageFacingEnabled) { auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation()); @@ -595,16 +594,9 @@ void MyAvatar::update(float deltaTime) { // draw hand azimuth vector glm::vec3 handAzimuthMidpoint = transformPoint(getTransform().getMatrix(), glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y)); - DebugDraw::getInstance().drawRay(getWorldPosition(), handAzimuthMidpoint, glm::vec4(0.0f, 1.0f, 1.0f, 1.0f)); - - + DebugDraw::getInstance().drawRay(getWorldPosition(), handAzimuthMidpoint, glm::vec4(0.0f, 1.0f, 1.0f, 1.0f)); } - // temp: draw spine 2 position for hand azimuth purposes. - int spine2Index = getJointIndex("Spine2"); - glm::vec3 spine2WorldPosition = transformPoint(getTransform().getMatrix(), getAbsoluteJointTranslationInObjectFrame(spine2Index)); - DebugDraw::getInstance().addMarker("spine2 location", Quaternions::IDENTITY, spine2WorldPosition, glm::vec4(1)); - if (_goToPending) { setWorldPosition(_goToPosition); setWorldOrientation(_goToOrientation); @@ -949,8 +941,6 @@ glm::vec2 MyAvatar::computeHandAzimuth() const { if (!(spine2Index < 0)) { // use the spine for the azimuth origin. azimuthOrigin = getAbsoluteJointTranslationInObjectFrame(spine2Index); - } else { - // use the avatar root as the azimuth origin. } controller::Pose leftHandPoseAvatarSpace = getLeftHandPose(); @@ -3882,6 +3872,10 @@ bool MyAvatar::getIsInSittingState() const { return _isInSittingState.get(); } +bool MyAvatar::getIsSitStandStateLocked() const { + return _lockSitStandState.get(); +} + float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } @@ -3907,6 +3901,11 @@ void MyAvatar::setIsInSittingState(bool isSitting) { emit sittingEnabledChanged(isSitting); } +void MyAvatar::setIsSitStandStateLocked(bool isLocked) { + _lockSitStandState.set(isLocked); + emit sitStandStateLockEnabledChanged(isLocked); +} + void MyAvatar::setWalkSpeed(float value) { _walkSpeed.set(value); } @@ -4152,7 +4151,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); bool returnValue = false; - returnValue = (offset.y > CYLINDER_TOP);// || (offset.y < CYLINDER_BOTTOM); + returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); if (myAvatar.getSitStandStateChange()) { qCDebug(interfaceapp) << "sit state change"; @@ -4206,7 +4205,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } } - + if (_velocityCount > 60) { if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); @@ -4216,7 +4215,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat _velocityCount++; } } - + } else { if (!isActive(Rotation) && getForceActivateRotation()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index f026f39493..674d4b8b70 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -244,6 +244,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed); Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); + Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; @@ -1105,6 +1106,8 @@ public: bool getIsInWalkingState() const; void setIsInSittingState(bool isSitting); bool getIsInSittingState() const; + void setIsSitStandStateLocked(bool isLocked); + bool getIsSitStandStateLocked() const; void setWalkSpeed(float value); float getWalkSpeed() const; void setWalkBackwardSpeed(float value); @@ -1526,6 +1529,14 @@ signals: */ void sittingEnabledChanged(bool enabled); + /**jsdoc + * Triggered when the sit state is enabled or disabled + * @function MyAvatar.sitStandStateLockEnabledChanged + * @param {boolean} enabled + * @returns {Signal} + */ + void sitStandStateLockEnabledChanged(bool enabled); + private slots: void leaveDomain(); void updateCollisionCapsuleCache(); @@ -1824,7 +1835,7 @@ private: float _sumUserHeightSensorSpace{ DEFAULT_AVATAR_HEIGHT }; int _averageUserHeightCount{ 1 }; bool _sitStandStateChange{ false }; - bool _lockSitStandState { true }; + ThreadSafeValueCache _lockSitStandState { true }; // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index ce8fefa0c5..78c5c03cc9 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -239,7 +239,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; // set spine2 if we have hand controllers - // myAvatar->getHMDLeanRecenterEnabled() && if (myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() && myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) { @@ -251,7 +250,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { bool headExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Head"), currentHeadPose); bool hipsExists = _rig.getAbsoluteJointPoseInRigFrame(_rig.indexOfJoint("Hips"), currentHipsPose); if (spine2Exists && headExists && hipsExists) { - // qCDebug(interfaceapp) << "hips forward direction "<< (currentHipsPose.rot() * glm::vec3(0.0f, 0.0f, 1.0f)); + AnimPose rigSpaceYaw(myAvatar->getSpine2RotationRigSpace()); glm::vec3 u, v, w; glm::vec3 fwd = rigSpaceYaw.rot() * glm::vec3(0.0f, 0.0f, 1.0f); diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index faf624392a..4a25ab9551 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -64,7 +64,8 @@ function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), collisionsEnabled : MyAvatar.getCollisionsEnabled(), - sittingEnabled : MyAvatar.isInSittingState, + sittingEnabled: MyAvatar.isInSittingState, + lockStateEnabled: MyAvatar.isSitStandStateLocked, collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), @@ -145,6 +146,14 @@ function onSittingEnabledChanged(isSitting) { } } +function onSitStandStateLockedEnabledChanged(isLocked) { + if (currentAvatarSettings.lockStateEnabled !== isLocked) { + currentAvatarSettings.lockStateEnabled = isLocked; + print("emit lock sit stand state changed"); + sendToQml({ 'method': 'settingChanged', 'name': 'lockStateEnabled', 'value': isLocked }) + } +} + function onNewCollisionSoundUrl(url) { if(currentAvatarSettings.collisionSoundUrl !== url) { currentAvatarSettings.collisionSoundUrl = url; @@ -324,6 +333,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See MyAvatar.setDominantHand(message.settings.dominantHand); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); MyAvatar.isInSittingState = message.settings.sittingEnabled; + MyAvatar.isSitStandStateLocked = message.settings.lockStateEnabled; MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); @@ -518,6 +528,7 @@ function off() { MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); MyAvatar.sittingEnabledChanged.disconnect(onSittingEnabledChanged); + MyAvatar.sitStandStateLockEnabledChanged.disconnect(onSitStandStateLockedEnabledChanged); MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); @@ -533,6 +544,7 @@ function on() { MyAvatar.dominantHandChanged.connect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); MyAvatar.sittingEnabledChanged.connect(onSittingEnabledChanged); + MyAvatar.sitStandStateLockEnabledChanged.connect(onSitStandStateLockedEnabledChanged); MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); From 4883a60afc9326c016ef37f617d70c2bca27a13a Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 11 Oct 2018 18:55:14 -0700 Subject: [PATCH 094/276] entity list type filter WIP - still some style issues --- scripts/system/html/css/edit-style.css | 46 +++++++++-- scripts/system/html/entityList.html | 20 +++-- scripts/system/html/js/entityList.js | 109 +++++++++++++++++++------ 3 files changed, 136 insertions(+), 39 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 6c1931932a..fc055174fb 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1031,30 +1031,60 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; /* New positioning context. */ } -#search-area { +#filter-area { padding-right: 168px; padding-bottom: 24px; } -#filter { - width: 98%; +#filter-type-multiselect { + position: relative; +} +#filter-type-selectBox { + position: absolute; +} +#filter-type-selectBox select { + font-weight: bold; + font-family: FiraSans-SemiBold; + color: #404040; + background-color: #afafaf; +} +#filter-type-checkboxes { + position: absolute; + top: 20px; + display: none; + border: 1px #dadada solid; +} +#filter-type-checkboxes label { + display: block; + font-family: FiraSans-SemiBold; + color: #404040; + background-color: #afafaf; +} +#filter-type-checkboxes label:hover { + background-color: #1e90ff; } -#in-view { +#filter-search-and-icon { + position: absolute; + left: 100px; + width: 60%; +} + +#filter-in-view { position: absolute; right: 126px; } -#radius-and-unit { +#filter-radius-and-unit { + position: relative; float: right; margin-right: -168px; - position: relative; top: -17px; } -#radius-and-unit label { +#filter-radius-and-unit label { margin-left: 2px; } -#radius-and-unit input { +#filter-radius-and-unit input { width: 120px; } diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 7eed78ecf3..8bcdc37d64 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -30,12 +30,22 @@
-
- Y - -
+
+
+
+ +
+
+ +
+
+
+ Y +
+ +
- +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index fed4dfb632..6b23d703ee 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -59,6 +59,18 @@ const COMPARE_DESCENDING = function(a, b) { return COMPARE_ASCENDING(b, a); } +const FILTER_TYPES = [ + "Shape", + "Model", + "Image", + "Light", + "Zone", + "Web", + "Material", + "ParticleEffect", + "Text", +]; + // List of all entities var entities = [] // List of all entities, indexed by Entity ID @@ -72,6 +84,7 @@ var entityList = null; // The ListView var currentSortColumn = 'type'; var currentSortOrder = ASCENDING_SORT; +var typeFilters = []; var isFilterInView = false; var showExtraInfo = false; @@ -105,9 +118,11 @@ function loaded() { elToggleLocked = document.getElementById("locked"); elToggleVisible = document.getElementById("visible"); elDelete = document.getElementById("delete"); - elFilter = document.getElementById("filter"); - elInView = document.getElementById("in-view") - elRadius = document.getElementById("radius"); + elFilterTypeSelectBox = document.getElementById("filter-type-selectBox"); + elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes"); + elFilterSearch = document.getElementById("filter-search"); + elFilterInView = document.getElementById("filter-in-view") + elFilterRadius = document.getElementById("filter-radius"); elExport = document.getElementById("export"); elPal = document.getElementById("pal"); elInfoToggle = document.getElementById("info-toggle"); @@ -171,13 +186,33 @@ function loaded() { elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); } - elFilter.onkeyup = refreshEntityList; - elFilter.onpaste = refreshEntityList; - elFilter.onchange = onFilterChange; - elFilter.onblur = refreshFooter; - elInView.onclick = toggleFilterInView; - elRadius.onchange = onRadiusChange; + elFilterSearch.onkeyup = refreshEntityList; + elFilterSearch.onpaste = refreshEntityList; + elFilterSearch.onchange = onFilterChange; + elFilterSearch.onblur = refreshFooter; + elFilterInView.onclick = toggleFilterInView; + elFilterRadius.onchange = onRadiusChange; elInfoToggle.onclick = toggleInfo; + + // create filter type dropdown checkboxes w/ label for each type + elFilterTypeSelectBox.onclick = toggleTypeDropdown; + for (let i = 0; i < FILTER_TYPES.length; ++i) { + let type = FILTER_TYPES[i]; + let typeFilterID = "filter-type-" + type; + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", typeFilterID); + elLabel.innerText = type; + let elInput = document.createElement('input'); + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", typeFilterID); + elInput.checked = true; // all types are checked initially + toggleTypeFilter(type, false); // add all types to the initial type filter + elInput.onclick = onToggleTypeFilter(type); + elDiv.appendChild(elInput); + elDiv.appendChild(elLabel); + elFilterTypeCheckboxes.appendChild(elDiv); + } elNoEntitiesInView.style.display = "none"; @@ -301,17 +336,16 @@ function loaded() { function refreshEntityList() { PROFILE("refresh-entity-list", function() { PROFILE("filter", function() { - let searchTerm = elFilter.value.toLowerCase(); - if (searchTerm === '') { - visibleEntities = entities.slice(0); - } else { - visibleEntities = entities.filter(function(e) { - return e.name.toLowerCase().indexOf(searchTerm) > -1 - || e.type.toLowerCase().indexOf(searchTerm) > -1 - || e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 - || e.id.toLowerCase().indexOf(searchTerm) > -1; - }); - } + let searchTerm = elFilterSearch.value.toLowerCase(); + visibleEntities = entities.filter(function(e) { + let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type; + let typeFilter = typeFilters.indexOf(type) > -1; + let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 || + e.type.toLowerCase().indexOf(searchTerm) > -1 || + e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 || + e.id.toLowerCase().indexOf(searchTerm) > -1); + return typeFilter && searchFilter; + }); }); PROFILE("sort", function() { @@ -588,10 +622,10 @@ function loaded() { function toggleFilterInView() { isFilterInView = !isFilterInView; if (isFilterInView) { - elInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); + elFilterInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); elNoEntitiesInView.style.display = "inline"; } else { - elInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); + elFilterInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); elNoEntitiesInView.style.display = "none"; } EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); @@ -604,12 +638,34 @@ function loaded() { } function onRadiusChange() { - elRadius.value = Math.max(elRadius.value, 0); - elNoEntitiesRadius.firstChild.nodeValue = elRadius.value; + elFilterRadius.value = Math.max(elFilterRadius.value, 0); + elNoEntitiesRadius.firstChild.nodeValue = elFilterRadius.value; elNoEntitiesMessage.style.display = "none"; - EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value })); + EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); refreshEntities(); } + + function toggleTypeDropdown() { + elFilterTypeCheckboxes.style.display = elFilterTypeCheckboxes.style.display === "block" ? "none" : "block"; + } + + function toggleTypeFilter(type, refresh) { + let typeFilterIndex = typeFilters.indexOf(type); + if (typeFilterIndex > -1) { + typeFilters.splice(typeFilterIndex, 1); + } else { + typeFilters.push(type); + } + if (refresh) { + refreshEntityList(); + } + } + + function onToggleTypeFilter(type) { + return function() { + toggleTypeFilter(type, true); + }; + } function toggleInfo(event) { showExtraInfo = !showExtraInfo; @@ -623,7 +679,7 @@ function loaded() { entityList.resize(); event.stopPropagation(); } - + document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { return; @@ -675,6 +731,7 @@ function loaded() { refreshSortOrder(); refreshEntities(); }); + augmentSpinButtons(); From 97438a08d931da46505830deaf6529943983ac9d Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 11 Oct 2018 23:04:33 -0700 Subject: [PATCH 095/276] styling fixes, add icon, adjust Types text, click outside to close --- scripts/system/html/css/edit-style.css | 80 ++++++++---- scripts/system/html/entityList.html | 7 +- scripts/system/html/js/entityList.js | 168 +++++++++++++++---------- 3 files changed, 165 insertions(+), 90 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index fc055174fb..da0d806a41 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1036,38 +1036,74 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { padding-bottom: 24px; } -#filter-type-multiselect { - position: relative; +#filter-area .multiselect { + position: relative; } -#filter-type-selectBox { - position: absolute; +#filter-area .selectBox { + position: absolute; } -#filter-type-selectBox select { - font-weight: bold; - font-family: FiraSans-SemiBold; - color: #404040; - background-color: #afafaf; +#filter-area .selectBox select { + font-family: FiraSans-SemiBold; + font-size: 15px; + color: #afafaf; + background-color: #252525; + border: none; + height: 28px; + width: 107px; + text-align-last: center; +} +#filter-area .overSelect { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; } #filter-type-checkboxes { - position: absolute; - top: 20px; - display: none; - border: 1px #dadada solid; + position: absolute; + z-index: 2; + top: 15px; + display: none; + border: none; +} +#filter-type-checkboxes div { + height: 19px; +} +#filter-type-checkboxes span { + font-family: hifi-glyphs; + font-size: 16px; + color: #404040; + padding: 12px 12px; + vertical-align: middle; } #filter-type-checkboxes label { - display: block; - font-family: FiraSans-SemiBold; - color: #404040; - background-color: #afafaf; + display: block; + font-family: FiraSans-SemiBold; + color: #404040; + background-color: #afafaf; + padding: 0 20px; + vertical-align: middle; } #filter-type-checkboxes label:hover { - background-color: #1e90ff; + background-color: #1e90ff; +} +#filter-type-checkboxes input[type=checkbox] { + display: block; + position: relative; + top: 17px; + right: -10px; +} +#filter-type-checkboxes input[type=checkbox] + label { + background-image: none; +} +#filter-type-checkboxes input[type=checkbox]:checked + label { + background-image: none; } #filter-search-and-icon { - position: absolute; - left: 100px; - width: 60%; + position: absolute; + left: 120px; + width: calc(100% - 300px); } #filter-in-view { @@ -1076,7 +1112,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { } #filter-radius-and-unit { - position: relative; + position: relative; float: right; margin-right: -168px; top: -17px; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 8bcdc37d64..e301f36945 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -31,9 +31,12 @@
-
+
- + +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 6b23d703ee..8812594408 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -60,17 +60,29 @@ const COMPARE_DESCENDING = function(a, b) { } const FILTER_TYPES = [ - "Shape", - "Model", - "Image", - "Light", - "Zone", - "Web", - "Material", - "ParticleEffect", - "Text", + "Shape", + "Model", + "Image", + "Light", + "Zone", + "Web", + "Material", + "ParticleEffect", + "Text", ]; +const ICON_FOR_TYPE = { + Shape: "n", + Model: "", + Image: "", + Light: "p", + Zone: "o", + Web: "q", + Material: "", + ParticleEffect: "", + Text: "l", +}; + // List of all entities var entities = [] // List of all entities, indexed by Entity ID @@ -118,8 +130,9 @@ function loaded() { elToggleLocked = document.getElementById("locked"); elToggleVisible = document.getElementById("visible"); elDelete = document.getElementById("delete"); - elFilterTypeSelectBox = document.getElementById("filter-type-selectBox"); - elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes"); + elFilterTypeSelectBox = document.getElementById("filter-type-selectBox"); + elFilterTypeText = document.getElementById("filter-type-text"); + elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes"); elFilterSearch = document.getElementById("filter-search"); elFilterInView = document.getElementById("filter-in-view") elFilterRadius = document.getElementById("filter-radius"); @@ -132,6 +145,8 @@ function loaded() { elNoEntitiesInView = document.getElementById("no-entities-in-view"); elNoEntitiesRadius = document.getElementById("no-entities-radius"); + document.body.onclick = onBodyClick; + document.getElementById("entity-name").onclick = function() { setSortColumn('name'); }; @@ -193,26 +208,30 @@ function loaded() { elFilterInView.onclick = toggleFilterInView; elFilterRadius.onchange = onRadiusChange; elInfoToggle.onclick = toggleInfo; - - // create filter type dropdown checkboxes w/ label for each type - elFilterTypeSelectBox.onclick = toggleTypeDropdown; - for (let i = 0; i < FILTER_TYPES.length; ++i) { - let type = FILTER_TYPES[i]; - let typeFilterID = "filter-type-" + type; - let elDiv = document.createElement('div'); - let elLabel = document.createElement('label'); - elLabel.setAttribute("for", typeFilterID); - elLabel.innerText = type; - let elInput = document.createElement('input'); - elInput.setAttribute("type", "checkbox"); - elInput.setAttribute("id", typeFilterID); - elInput.checked = true; // all types are checked initially - toggleTypeFilter(type, false); // add all types to the initial type filter - elInput.onclick = onToggleTypeFilter(type); - elDiv.appendChild(elInput); - elDiv.appendChild(elLabel); - elFilterTypeCheckboxes.appendChild(elDiv); - } + + // create filter type dropdown checkboxes w/ label for each type + elFilterTypeSelectBox.onclick = toggleTypeDropdown; + for (let i = 0; i < FILTER_TYPES.length; ++i) { + let type = FILTER_TYPES[i]; + let typeFilterID = "filter-type-" + type; + let elDiv = document.createElement('div'); + let elLabel = document.createElement('label'); + elLabel.setAttribute("for", typeFilterID); + elLabel.innerText = type; + let elSpan = document.createElement('span'); + elSpan.setAttribute("class", "typeIcon"); + elSpan.innerHTML = ICON_FOR_TYPE[type]; + let elInput = document.createElement('input'); + elInput.setAttribute("type", "checkbox"); + elInput.setAttribute("id", typeFilterID); + elInput.checked = true; // all types are checked initially + toggleTypeFilter(type, false); // add all types to the initial type filter + elInput.onclick = onToggleTypeFilter(type); + elDiv.appendChild(elInput); + elLabel.insertBefore(elSpan, elLabel.childNodes[0]); + elDiv.appendChild(elLabel); + elFilterTypeCheckboxes.appendChild(elDiv); + } elNoEntitiesInView.style.display = "none"; @@ -336,16 +355,16 @@ function loaded() { function refreshEntityList() { PROFILE("refresh-entity-list", function() { PROFILE("filter", function() { - let searchTerm = elFilterSearch.value.toLowerCase(); - visibleEntities = entities.filter(function(e) { - let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type; - let typeFilter = typeFilters.indexOf(type) > -1; - let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 || - e.type.toLowerCase().indexOf(searchTerm) > -1 || - e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 || - e.id.toLowerCase().indexOf(searchTerm) > -1); - return typeFilter && searchFilter; - }); + let searchTerm = elFilterSearch.value.toLowerCase(); + visibleEntities = entities.filter(function(e) { + let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type; + let typeFilter = typeFilters.indexOf(type) > -1; + let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 || + e.type.toLowerCase().indexOf(searchTerm) > -1 || + e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 || + e.id.toLowerCase().indexOf(searchTerm) > -1); + return typeFilter && searchFilter; + }); }); PROFILE("sort", function() { @@ -644,29 +663,38 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); refreshEntities(); } - - function toggleTypeDropdown() { - elFilterTypeCheckboxes.style.display = elFilterTypeCheckboxes.style.display === "block" ? "none" : "block"; - } - - function toggleTypeFilter(type, refresh) { - let typeFilterIndex = typeFilters.indexOf(type); - if (typeFilterIndex > -1) { - typeFilters.splice(typeFilterIndex, 1); - } else { - typeFilters.push(type); - } - if (refresh) { - refreshEntityList(); - } - } - - function onToggleTypeFilter(type) { - return function() { - toggleTypeFilter(type, true); - }; - } - + + function toggleTypeDropdown() { + elFilterTypeCheckboxes.style.display = elFilterTypeCheckboxes.style.display === "block" ? "none" : "block"; + } + + function toggleTypeFilter(type, refresh) { + let typeFilterIndex = typeFilters.indexOf(type); + if (typeFilterIndex > -1) { + typeFilters.splice(typeFilterIndex, 1); + } else { + typeFilters.push(type); + } + + if (typeFilters.length === 0) { + elFilterTypeText.innerText = "No Types"; + } else if (typeFilters.length === FILTER_TYPES.length) { + elFilterTypeText.innerText = "All Types"; + } else { + elFilterTypeText.innerText = "Types..."; + } + + if (refresh) { + refreshEntityList(); + } + } + + function onToggleTypeFilter(type) { + return function() { + toggleTypeFilter(type, true); + }; + } + function toggleInfo(event) { showExtraInfo = !showExtraInfo; if (showExtraInfo) { @@ -679,7 +707,15 @@ function loaded() { entityList.resize(); event.stopPropagation(); } - + + function onBodyClick(event) { + let targetNode = event.target; + if (!elFilterTypeSelectBox.contains(targetNode) && !elFilterTypeCheckboxes.contains(targetNode) && + elFilterTypeCheckboxes.style.display === "block") { + toggleTypeDropdown(); + } + } + document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { return; @@ -731,7 +767,7 @@ function loaded() { refreshSortOrder(); refreshEntities(); }); - + augmentSpinButtons(); From 52ed6cb6e95299c578b2c3803ed42959c192d040 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 12 Oct 2018 11:35:51 -0700 Subject: [PATCH 096/276] Abort settle animation if already moving --- interface/src/avatar/AvatarManager.cpp | 6 +++++- .../avatars-renderer/src/avatars-renderer/Avatar.cpp | 8 +++++++- libraries/avatars-renderer/src/avatars-renderer/Avatar.h | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 80e4e4ba66..5fb9e0e0af 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -168,6 +168,10 @@ void AvatarManager::playTransitAnimations(AvatarTransit::Status status) { qDebug() << "END_FRAME"; _myAvatar->restoreAnimation(); break; + case AvatarTransit::Status::PRE_TRANSIT_IDLE: + break; + case AvatarTransit::Status::POST_TRANSIT_IDLE: + break; case AvatarTransit::Status::IDLE: break; case AvatarTransit::Status::TRANSITING: @@ -180,7 +184,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); - if (status != AvatarTransit::Status::IDLE) { + if (status != AvatarTransit::Status::IDLE && status != AvatarTransit::Status::PRE_TRANSIT_IDLE && status != AvatarTransit::Status::POST_TRANSIT_IDLE) { playTransitAnimations(status); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 53c2019138..d8587b6086 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -127,6 +127,10 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av } _lastPosition = avatarPosition; _status = updatePosition(deltaTime); + if (_isTransiting && oneFrameDistance > 0.1f && _status == Status::POST_TRANSIT_IDLE) { + reset(); + _status = Status::END_FRAME; + } return _status; } @@ -177,18 +181,20 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { float nextTime = _currentTime + deltaTime; if (nextTime < _preTime) { _currentPosition = _startPosition; + status = Status::PRE_TRANSIT_IDLE; if (_currentTime == 0) { status = Status::START_FRAME; } } else if (nextTime < _totalTime - _postTime){ + status = Status::TRANSITING; if (_currentTime <= _preTime) { status = Status::START_TRANSIT; } else { float percentageIntoTransit = (nextTime - _preTime) / _transitTime; _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; - status = Status::TRANSITING; } } else { + status = Status::POST_TRANSIT_IDLE; _currentPosition = _endPosition; if (nextTime >= _totalTime) { _isTransiting = false; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index ffe90ecaa2..fe163b9dae 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -55,9 +55,11 @@ public: enum Status { IDLE = 0, START_FRAME, + PRE_TRANSIT_IDLE, START_TRANSIT, TRANSITING, END_TRANSIT, + POST_TRANSIT_IDLE, END_FRAME, ABORT_TRANSIT }; From 6320f9bab7f6a34fde523e9544c75dbd83b0919e Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Oct 2018 11:39:54 -0700 Subject: [PATCH 097/276] tweaks --- scripts/system/html/css/edit-style.css | 26 ++++++++++++++++-------- scripts/system/html/js/entityList.js | 28 +++++++++++++++----------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index da0d806a41..8179b95e35 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1036,13 +1036,13 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { padding-bottom: 24px; } -#filter-area .multiselect { +.multiselect { position: relative; } -#filter-area .selectBox { +.selectBox { position: absolute; } -#filter-area .selectBox select { +.selectBox select { font-family: FiraSans-SemiBold; font-size: 15px; color: #afafaf; @@ -1052,22 +1052,24 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { width: 107px; text-align-last: center; } -#filter-area .overSelect { +.overSelect { position: absolute; left: 0; right: 0; top: 0; bottom: 0; } + #filter-type-checkboxes { position: absolute; z-index: 2; - top: 15px; + top: 28px; display: none; border: none; } #filter-type-checkboxes div { - height: 19px; + position: relative; + height: 25px; } #filter-type-checkboxes span { font-family: hifi-glyphs; @@ -1077,21 +1079,26 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { vertical-align: middle; } #filter-type-checkboxes label { + position: relative; + top: -13px; + z-index: 3; display: block; font-family: FiraSans-SemiBold; color: #404040; background-color: #afafaf; padding: 0 20px; + height: 25px; vertical-align: middle; } #filter-type-checkboxes label:hover { background-color: #1e90ff; } #filter-type-checkboxes input[type=checkbox] { - display: block; position: relative; - top: 17px; + top: 4px; right: -10px; + z-index: 4; + display: block; } #filter-type-checkboxes input[type=checkbox] + label { background-image: none; @@ -1099,6 +1106,9 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { #filter-type-checkboxes input[type=checkbox]:checked + label { background-image: none; } +#filter-type-checkboxes input[type=checkbox]:hover + label { + background-color: #1e90ff; +} #filter-search-and-icon { position: absolute; diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 8812594408..f780fd0e2e 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -146,7 +146,6 @@ function loaded() { elNoEntitiesRadius = document.getElementById("no-entities-radius"); document.body.onclick = onBodyClick; - document.getElementById("entity-name").onclick = function() { setSortColumn('name'); }; @@ -209,7 +208,7 @@ function loaded() { elFilterRadius.onchange = onRadiusChange; elInfoToggle.onclick = toggleInfo; - // create filter type dropdown checkboxes w/ label for each type + // create filter type dropdown checkboxes with label and icon for each type elFilterTypeSelectBox.onclick = toggleTypeDropdown; for (let i = 0; i < FILTER_TYPES.length; ++i) { let type = FILTER_TYPES[i]; @@ -225,7 +224,7 @@ function loaded() { elInput.setAttribute("type", "checkbox"); elInput.setAttribute("id", typeFilterID); elInput.checked = true; // all types are checked initially - toggleTypeFilter(type, false); // add all types to the initial type filter + toggleTypeFilter(type, false); // add all types to the initial types filter elInput.onclick = onToggleTypeFilter(type); elDiv.appendChild(elInput); elLabel.insertBefore(elSpan, elLabel.childNodes[0]); @@ -664,8 +663,12 @@ function loaded() { refreshEntities(); } + function isTypeDropdownVisible() { + return elFilterTypeCheckboxes.style.display === "block"; + } + function toggleTypeDropdown() { - elFilterTypeCheckboxes.style.display = elFilterTypeCheckboxes.style.display === "block" ? "none" : "block"; + elFilterTypeCheckboxes.style.display = isTypeDropdownVisible() ? "none" : "block"; } function toggleTypeFilter(type, refresh) { @@ -695,6 +698,15 @@ function loaded() { }; } + function onBodyClick(event) { + // if clicking anywhere outside of the type filter dropdown and it's open then close it + let elTarget = event.target; + if (isTypeDropdownVisible() && !elFilterTypeSelectBox.contains(elTarget) && + !elFilterTypeCheckboxes.contains(elTarget)) { + toggleTypeDropdown(); + } + } + function toggleInfo(event) { showExtraInfo = !showExtraInfo; if (showExtraInfo) { @@ -707,14 +719,6 @@ function loaded() { entityList.resize(); event.stopPropagation(); } - - function onBodyClick(event) { - let targetNode = event.target; - if (!elFilterTypeSelectBox.contains(targetNode) && !elFilterTypeCheckboxes.contains(targetNode) && - elFilterTypeCheckboxes.style.display === "block") { - toggleTypeDropdown(); - } - } document.addEventListener("keydown", function (keyDownEvent) { if (keyDownEvent.target.nodeName === "INPUT") { From d7dc69129aed97cb6808101e504d92d5a478e6e7 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 12 Oct 2018 11:43:03 -0700 Subject: [PATCH 098/276] improve loading bar progress --- .../resources/sounds/crystals_and_voices.mp3 | Bin 0 -> 337547 bytes scripts/system/interstitialPage.js | 23 +++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 interface/resources/sounds/crystals_and_voices.mp3 diff --git a/interface/resources/sounds/crystals_and_voices.mp3 b/interface/resources/sounds/crystals_and_voices.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b5bee31381639f66aaf6c2ba0724ea7fa9620268 GIT binary patch literal 337547 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%0D!?6J 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 =4U{m+sJ@}sfm+aFA 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

Qd%Ka2Apmb~P_L>@7Kk>gbHUQ;C^H}3_k<9&MH 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 (TOTAL_LOADING_PROGRESS * 0.4)) { + if (currentProgress >= ((TOTAL_LOADING_PROGRESS * 0.4) - TEXTURE_EPSILON)) { var textureResourceGPUMemSize = renderStats.textureResourceGPUMemSize; var texturePopulatedGPUMemSize = renderStats.textureResourcePopulatedGPUMemSize; @@ -472,10 +474,9 @@ textureMemSizeAtLastCheck = textureResourceGPUMemSize; - if (textureMemSizeStabilityCount >= 20) { + if (textureMemSizeStabilityCount >= 30) { if (textureResourceGPUMemSize > 0) { - // print((texturePopulatedGPUMemSize / textureResourceGPUMemSize)); var gpuPercantage = (TOTAL_LOADING_PROGRESS * 0.6) * (texturePopulatedGPUMemSize / textureResourceGPUMemSize); var totalProgress = progress + gpuPercantage; if (totalProgress >= target) { @@ -489,7 +490,11 @@ target = TOTAL_LOADING_PROGRESS; } - currentProgress = lerp(currentProgress, target, 0.2); + if (deltaTime > 1.0) { + deltaTimeMS = 0.02; + } + + currentProgress = lerp(currentProgress, target, (deltaTimeMS * 2.0)); var properties = { localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / TOTAL_LOADING_PROGRESS))), y: -0.935, z: 0.0 }, dimensions: { From fa6b3019f6768ec3901e1e74dd5ea2972a3ff2e8 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 12 Oct 2018 12:07:39 -0700 Subject: [PATCH 099/276] fix js memory leak --- .../controllers/controllerModules/webSurfaceLaserInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 898164dc99..229f1c411f 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -109,7 +109,7 @@ Script.include("/~/system/libraries/controllers.js"); Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); } - this.ignoredOverlays = []; + this.ignoredObjects = []; }; this.isPointingAtTriggerable = function(controllerData, triggerPressed, checkEntitiesOnly) { From 7dd156ccdd790bbc750d05465ad4ad3de6698067 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 12 Oct 2018 15:00:15 -0700 Subject: [PATCH 100/276] made the threshold for the spine angle larger to make auto transitions more robust --- interface/src/avatar/MyAvatar.cpp | 57 +++++++++++++++--------- interface/src/avatar/MyAvatar.h | 2 +- interface/src/avatar/MySkeletonModel.cpp | 2 +- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 745705f7b8..e130f78dc4 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -471,6 +471,7 @@ void MyAvatar::update(float deltaTime) { const float STANDING_HEIGHT_MULTIPLE = 1.2f; const float SITTING_HEIGHT_MULTIPLE = 0.833f; const float COSINE_TEN_DEGREES = 0.98f; + const float COSINE_THIRTY_DEGREES = 0.866f; const int SITTING_COUNT_THRESHOLD = 300; const int SQUATTY_COUNT_THRESHOLD = 600; @@ -518,7 +519,7 @@ void MyAvatar::update(float deltaTime) { upSpine2 = glm::normalize(upSpine2); } float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); - if (getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && (angleSpine2 > COSINE_TEN_DEGREES)) { + if (getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { _squatCount++; } else { _squatCount = 0; @@ -531,7 +532,7 @@ void MyAvatar::update(float deltaTime) { glm::vec3 sensorHips = transformPoint(glm::inverse(getSensorToWorldMatrix()), worldHips); // put update sit stand state counts here - if (getIsSitStandStateLocked()) { + if (!getIsSitStandStateLocked() && (_follow._velocityCount > 60)) { if (getIsInSittingState()) { if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { // if we recenter upwards then no longer in sitting state @@ -544,10 +545,10 @@ void MyAvatar::update(float deltaTime) { _tippingPoint = newHeightReading.getTranslation().y; } _averageUserHeightCount = 1; - setResetMode(true); + // setResetMode(true); setIsInSittingState(false); - setCenterOfGravityModelEnabled(true); - setSitStandStateChange(true); + // setCenterOfGravityModelEnabled(true); + // setSitStandStateChange(true); } qCDebug(interfaceapp) << "going to stand state"; } else { @@ -557,7 +558,7 @@ void MyAvatar::update(float deltaTime) { } } else { // in the standing state - if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint))) {// && (angleSpine2 > COSINE_TEN_DEGREES) && !(sensorHips.y > (0.4f * averageSensorSpaceHeight))) { + if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { _sitStandStateCount++; if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { _sitStandStateCount = 0; @@ -567,11 +568,10 @@ void MyAvatar::update(float deltaTime) { _tippingPoint = newHeightReading.getTranslation().y; } _averageUserHeightCount = 1; - setResetMode(true); + // setResetMode(true); setIsInSittingState(true); - setCenterOfGravityModelEnabled(false); - - setSitStandStateChange(true); + // setCenterOfGravityModelEnabled(false); + // setSitStandStateChange(true); } qCDebug(interfaceapp) << "going to sit state"; } else { @@ -3898,6 +3898,13 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { void MyAvatar::setIsInSittingState(bool isSitting) { _isInSittingState.set(isSitting); + setResetMode(true); + if (isSitting) { + setCenterOfGravityModelEnabled(false); + } else { + setCenterOfGravityModelEnabled(true); + } + setSitStandStateChange(true); emit sittingEnabledChanged(isSitting); } @@ -4146,32 +4153,34 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl const float CYLINDER_TOP = 0.1f; const float CYLINDER_BOTTOM = -1.5f; const float SITTING_BOTTOM = -0.02f; - const int SQUATTY_COUNT_THRESHOLD = 600; + const int SQUATTY_COUNT_THRESHOLD = 1800; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); bool returnValue = false; - returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); if (myAvatar.getSitStandStateChange()) { - qCDebug(interfaceapp) << "sit state change"; + // qCDebug(interfaceapp) << "sit state change"; returnValue = true; + } if (myAvatar.getIsInSittingState()) { + if (myAvatar.getIsSitStandStateLocked()) { + returnValue = (offset.y > CYLINDER_TOP); + } if (offset.y < SITTING_BOTTOM) { // we recenter when sitting. - qCDebug(interfaceapp) << "lean back sitting "; + // qCDebug(interfaceapp) << "lean back sitting "; returnValue = true; } } else { // in the standing state - if (myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) { + returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + // finally check for squats in standing + if ((myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) && !myAvatar.getIsSitStandStateLocked()) { myAvatar._squatCount = 0; - qCDebug(interfaceapp) << "squatting "; - // returnValue = true; - } - if (returnValue == true) { - qCDebug(interfaceapp) << "above or below capsule in standing"; + // qCDebug(interfaceapp) << "squatting "; + returnValue = true; } } return returnValue; @@ -4185,6 +4194,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); + qCDebug(interfaceapp) << "should activate rotation true "; myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } if (myAvatar.getCenterOfGravityModelEnabled()) { @@ -4199,7 +4209,8 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat // center of gravity model is not enabled if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Horizontal); - if (myAvatar.getEnableStepResetRotation()) { + if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) { + qCDebug(interfaceapp) << "step recenter rotation true "; activate(Rotation); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } @@ -4214,6 +4225,10 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if ((glm::length(myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD).getVelocity()) > 0.1f)) { _velocityCount++; } + if (_velocityCount > 60) { + qCDebug(interfaceapp) << "reached velocity count "; + } + } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 674d4b8b70..10b5510e4d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1835,7 +1835,7 @@ private: float _sumUserHeightSensorSpace{ DEFAULT_AVATAR_HEIGHT }; int _averageUserHeightCount{ 1 }; bool _sitStandStateChange{ false }; - ThreadSafeValueCache _lockSitStandState { true }; + ThreadSafeValueCache _lockSitStandState { false }; // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 78c5c03cc9..e5b9df7dd1 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 && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState())) { + if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying && !(myAvatar->getIsInWalkingState()) && !(myAvatar->getIsInSittingState()) && myAvatar->getHMDLeanRecenterEnabled()) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); } else { From ee830cdcb0ef19f7f731d6032eccefe957a7fec3 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 12 Oct 2018 16:05:31 -0700 Subject: [PATCH 101/276] cleaned white space --- interface/src/avatar/MyAvatar.cpp | 40 +++++++++---------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e130f78dc4..5e4db283a3 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -470,7 +470,6 @@ void MyAvatar::update(float deltaTime) { const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders const float STANDING_HEIGHT_MULTIPLE = 1.2f; const float SITTING_HEIGHT_MULTIPLE = 0.833f; - const float COSINE_TEN_DEGREES = 0.98f; const float COSINE_THIRTY_DEGREES = 0.866f; const int SITTING_COUNT_THRESHOLD = 300; const int SQUATTY_COUNT_THRESHOLD = 600; @@ -545,12 +544,8 @@ void MyAvatar::update(float deltaTime) { _tippingPoint = newHeightReading.getTranslation().y; } _averageUserHeightCount = 1; - // setResetMode(true); setIsInSittingState(false); - // setCenterOfGravityModelEnabled(true); - // setSitStandStateChange(true); } - qCDebug(interfaceapp) << "going to stand state"; } else { _sitStandStateCount = 0; // tipping point is average height when sitting. @@ -568,19 +563,13 @@ void MyAvatar::update(float deltaTime) { _tippingPoint = newHeightReading.getTranslation().y; } _averageUserHeightCount = 1; - // setResetMode(true); setIsInSittingState(true); - // setCenterOfGravityModelEnabled(false); - // setSitStandStateChange(true); } - qCDebug(interfaceapp) << "going to sit state"; } else { - // returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); // use the mode height for the tipping point when we are standing. _tippingPoint = getCurrentStandingHeight(); _sitStandStateCount = 0; } - } } @@ -594,7 +583,7 @@ void MyAvatar::update(float deltaTime) { // draw hand azimuth vector glm::vec3 handAzimuthMidpoint = transformPoint(getTransform().getMatrix(), glm::vec3(_hipToHandController.x, 0.0f, _hipToHandController.y)); - DebugDraw::getInstance().drawRay(getWorldPosition(), handAzimuthMidpoint, glm::vec4(0.0f, 1.0f, 1.0f, 1.0f)); + DebugDraw::getInstance().drawRay(getWorldPosition(), handAzimuthMidpoint, glm::vec4(0.0f, 1.0f, 1.0f, 1.0f)); } if (_goToPending) { @@ -3909,7 +3898,12 @@ void MyAvatar::setIsInSittingState(bool isSitting) { } void MyAvatar::setIsSitStandStateLocked(bool isLocked) { + const float DEFAULT_FLOOR_HEIGHT = 0.0f; _lockSitStandState.set(isLocked); + _sitStandStateCount = 0; + _sumUserHeightSensorSpace = DEFAULT_AVATAR_HEIGHT; + _tippingPoint = DEFAULT_FLOOR_HEIGHT; + _averageUserHeightCount = 1; emit sitStandStateLockEnabledChanged(isLocked); } @@ -4156,21 +4150,17 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl const int SQUATTY_COUNT_THRESHOLD = 1800; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); - bool returnValue = false; if (myAvatar.getSitStandStateChange()) { - // qCDebug(interfaceapp) << "sit state change"; returnValue = true; - } if (myAvatar.getIsInSittingState()) { if (myAvatar.getIsSitStandStateLocked()) { returnValue = (offset.y > CYLINDER_TOP); } if (offset.y < SITTING_BOTTOM) { - // we recenter when sitting. - // qCDebug(interfaceapp) << "lean back sitting "; + // we recenter more easily when in sitting state. returnValue = true; } } else { @@ -4179,7 +4169,6 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl // finally check for squats in standing if ((myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) && !myAvatar.getIsSitStandStateLocked()) { myAvatar._squatCount = 0; - // qCDebug(interfaceapp) << "squatting "; returnValue = true; } } @@ -4194,7 +4183,6 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); - qCDebug(interfaceapp) << "should activate rotation true "; myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } if (myAvatar.getCenterOfGravityModelEnabled()) { @@ -4210,28 +4198,22 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Horizontal); if (myAvatar.getEnableStepResetRotation() && !myAvatar.getIsInSittingState()) { - qCDebug(interfaceapp) << "step recenter rotation true "; activate(Rotation); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); } } } - - if (_velocityCount > 60) { + const int VELOCITY_COUNT_THRESHOLD = 60; + const float MINIMUM_HMD_VELOCITY = 0.1f; + if (_velocityCount > VELOCITY_COUNT_THRESHOLD) { if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); } } else { - if ((glm::length(myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD).getVelocity()) > 0.1f)) { + if ((glm::length(myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD).getVelocity()) > MINIMUM_HMD_VELOCITY)) { _velocityCount++; } - if (_velocityCount > 60) { - qCDebug(interfaceapp) << "reached velocity count "; - } - } - - } else { if (!isActive(Rotation) && getForceActivateRotation()) { activate(Rotation); From 90c9e578c845df4661adeda1f8d3809d2fb9fe53 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 12 Oct 2018 16:47:53 -0700 Subject: [PATCH 102/276] when you unlock the transitions you now start in stand state --- interface/src/avatar/MyAvatar.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5e4db283a3..172fdac96c 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3904,6 +3904,10 @@ void MyAvatar::setIsSitStandStateLocked(bool isLocked) { _sumUserHeightSensorSpace = DEFAULT_AVATAR_HEIGHT; _tippingPoint = DEFAULT_FLOOR_HEIGHT; _averageUserHeightCount = 1; + if (!isLocked) { + // always start the auto transition mode in standing state. + setIsInSittingState(false); + } emit sitStandStateLockEnabledChanged(isLocked); } From a85336044f0201afa66fd287e5d9cdca13a99b1c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Oct 2018 18:03:47 -0700 Subject: [PATCH 103/276] add X to clear button to filter search --- scripts/system/html/css/edit-style.css | 14 +++++++++++++- scripts/system/html/entityList.html | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8179b95e35..69fed02099 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -198,7 +198,7 @@ td.url { } -input[type="text"], input[type="number"], textarea { +input[type="text"], input[type="search"], input[type="number"], textarea { margin: 0; padding: 0 12px; color: #afafaf; @@ -257,6 +257,18 @@ input[type="text"] { width: 100%; } +input[type="search"] { + height: 28px; + width: 100%; +} +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; + height: 20px; + width: 20px; + border-radius:10px; + background-image: url('data:image/png;base64,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') +} + input[type="number"] { position: relative; height: 28px; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index e301f36945..626402362d 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -43,7 +43,7 @@

- Y + Y
From 978cd4bb7764cb4bbc017da1164043d0f509d75c Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Oct 2018 18:57:26 -0700 Subject: [PATCH 104/276] styling tweaks, fix X clear search not refreshing --- scripts/system/html/css/edit-style.css | 15 ++++++++------- scripts/system/html/entityList.html | 2 +- scripts/system/html/js/entityList.js | 9 +-------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 69fed02099..699c27448a 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -265,7 +265,6 @@ input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; height: 20px; width: 20px; - border-radius:10px; background-image: url('data:image/png;base64,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') } @@ -1084,11 +1083,13 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { height: 25px; } #filter-type-checkboxes span { + position: relative; + top: 3px; font-family: hifi-glyphs; font-size: 16px; color: #404040; - padding: 12px 12px; - vertical-align: middle; + padding-left: 12px; + padding-right: 12px; } #filter-type-checkboxes label { position: relative; @@ -1098,16 +1099,16 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { font-family: FiraSans-SemiBold; color: #404040; background-color: #afafaf; - padding: 0 20px; - height: 25px; - vertical-align: middle; + padding-top: 1px; + padding-right: 12px; + height: 24px; } #filter-type-checkboxes label:hover { background-color: #1e90ff; } #filter-type-checkboxes input[type=checkbox] { position: relative; - top: 4px; + top: 6px; right: -10px; z-index: 4; display: block; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 626402362d..06c2be8e73 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -39,7 +39,7 @@
- +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index f780fd0e2e..0f3f27a547 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -201,9 +201,7 @@ function loaded() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); } elFilterSearch.onkeyup = refreshEntityList; - elFilterSearch.onpaste = refreshEntityList; - elFilterSearch.onchange = onFilterChange; - elFilterSearch.onblur = refreshFooter; + elFilterSearch.onsearch = refreshEntityList; elFilterInView.onclick = toggleFilterInView; elFilterRadius.onchange = onRadiusChange; elInfoToggle.onclick = toggleInfo; @@ -650,11 +648,6 @@ function loaded() { refreshEntities(); } - function onFilterChange() { - refreshEntityList(); - entityList.resize(); - } - function onRadiusChange() { elFilterRadius.value = Math.max(elFilterRadius.value, 0); elNoEntitiesRadius.firstChild.nodeValue = elFilterRadius.value; From ad09007bd8f20f62618871b105bd288551534d26 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sat, 13 Oct 2018 16:32:51 -0700 Subject: [PATCH 105/276] Fix flickering holded entity, renames and refactors --- interface/src/avatar/AvatarActionHold.cpp | 4 +- interface/src/avatar/AvatarManager.cpp | 26 ++++--- interface/src/avatar/AvatarManager.h | 2 +- .../src/avatars-renderer/Avatar.cpp | 73 +++++++++---------- .../src/avatars-renderer/Avatar.h | 20 +++-- 5 files changed, 64 insertions(+), 61 deletions(-) diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 230f8aa64b..53074ac4ba 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -135,7 +135,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: glm::vec3 palmPosition; glm::quat palmRotation; - bool isTransitingWithAvatar = holdingAvatar->getTransit()->isTransiting(); + bool isTransitingWithAvatar = holdingAvatar->getTransit()->isActive(); if (isTransitingWithAvatar != _isTransitingWithAvatar) { _isTransitingWithAvatar = isTransitingWithAvatar; auto ownerEntity = _ownerEntity.lock(); @@ -424,7 +424,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { if (ownerEntity) { ownerEntity->setDynamicDataDirty(true); ownerEntity->setDynamicDataNeedsTransmit(true); - ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isTransiting()); + ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isActive()); } }); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 5fb9e0e0af..c1fddaa680 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -142,14 +142,14 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) { _space = space; } -void AvatarManager::playTransitAnimations(AvatarTransit::Status status) { +void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { auto startAnimation = _transitConfig._startTransitAnimation; auto middleAnimation = _transitConfig._middleTransitAnimation; auto endAnimation = _transitConfig._endTransitAnimation; const float REFERENCE_FPS = 30.0f; switch (status) { - case AvatarTransit::Status::START_FRAME: + case AvatarTransit::Status::STARTED: qDebug() << "START_FRAME"; _myAvatar->overrideAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, startAnimation._firstFrame + startAnimation._frameCount); @@ -164,18 +164,20 @@ void AvatarManager::playTransitAnimations(AvatarTransit::Status status) { _myAvatar->overrideAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, endAnimation._firstFrame + endAnimation._frameCount); break; - case AvatarTransit::Status::END_FRAME: + case AvatarTransit::Status::ENDED: qDebug() << "END_FRAME"; _myAvatar->restoreAnimation(); break; - case AvatarTransit::Status::PRE_TRANSIT_IDLE: + case AvatarTransit::Status::PRE_TRANSIT: break; - case AvatarTransit::Status::POST_TRANSIT_IDLE: + case AvatarTransit::Status::POST_TRANSIT: break; case AvatarTransit::Status::IDLE: break; case AvatarTransit::Status::TRANSITING: break; + case AvatarTransit::Status::ABORT_TRANSIT: + break; } } @@ -184,9 +186,10 @@ void AvatarManager::updateMyAvatar(float deltaTime) { PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); - if (status != AvatarTransit::Status::IDLE && status != AvatarTransit::Status::PRE_TRANSIT_IDLE && status != AvatarTransit::Status::POST_TRANSIT_IDLE) { - playTransitAnimations(status); - } + handleTransitAnimations(status); + + bool sendFirstTransitPacket = (status == AvatarTransit::Status::STARTED); + bool sendCurrentTransitPacket = (status != AvatarTransit::Status::IDLE && (status != AvatarTransit::Status::POST_TRANSIT || status != AvatarTransit::Status::ENDED)); _myAvatar->update(deltaTime); render::Transaction transaction; @@ -196,9 +199,14 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { + if (sendFirstTransitPacket || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused)) { // send head/hand data to the avatar mixer and voxel server PerformanceTimer perfTimer("send"); + if (sendFirstTransitPacket) { + _myAvatar->overrideNextPacketPositionData(_myAvatar->getTransit()->getEndPosition()); + } else if (sendCurrentTransitPacket) { + _myAvatar->overrideNextPacketPositionData(_myAvatar->getTransit()->getCurrentPosition()); + } _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 0dc39e991b..0f82aa7329 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -213,7 +213,7 @@ private: // frequently grabs a read lock on the hash to get a given avatar by ID void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; - void playTransitAnimations(AvatarTransit::Status status); + void handleTransitAnimations(AvatarTransit::Status status); QVector _avatarsToFade; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d8587b6086..b2747277c9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -114,10 +114,12 @@ void Avatar::setShowNamesAboveHeads(bool show) { } AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { - float oneFrameDistance = _isTransiting ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); - const float MAX_TRANSIT_DISTANCE = 30.0f; - float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale; - if (oneFrameDistance > config._triggerDistance) { + bool checkDistance = (!_isActive || (_status == Status::POST_TRANSIT || _status == Status::ENDED)); + float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); + + const float TRANSIT_TRIGGER_MAX_DISTANCE = 30.0f; + float scaledMaxTransitDistance = TRANSIT_TRIGGER_MAX_DISTANCE * _scale; + if (oneFrameDistance > config._triggerDistance && checkDistance) { if (oneFrameDistance < scaledMaxTransitDistance) { start(deltaTime, _lastPosition, avatarPosition, config); } else { @@ -127,9 +129,12 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av } _lastPosition = avatarPosition; _status = updatePosition(deltaTime); - if (_isTransiting && oneFrameDistance > 0.1f && _status == Status::POST_TRANSIT_IDLE) { + + const float SETTLE_ABORT_DISTANCE = 0.1f; + float scaledSettleAbortDistance = SETTLE_ABORT_DISTANCE * _scale; + if (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT) { reset(); - _status = Status::END_FRAME; + _status = Status::ENDED; } return _status; } @@ -137,7 +142,7 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av void AvatarTransit::reset() { _lastPosition = _endPosition; _currentPosition = _endPosition; - _isTransiting = false; + _isActive = false; } void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const AvatarTransit::TransitConfig& config) { _startPosition = startPosition; @@ -147,14 +152,14 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const _totalDistance = glm::length(_transitLine); _easeType = config._easeType; const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - _preTime = (float)config._startTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; - _postTime = (float)config._endTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + _preTransitTime = (float)config._startTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + _postTransitTime = (float)config._endTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; - _totalTime = _transitTime + _preTime + _postTime; - _currentTime = _isTransiting ? _preTime : 0.0f; - _isTransiting = true; + _totalTime = _transitTime + _preTransitTime + _postTransitTime; + _currentTime = _isActive ? _preTransitTime : 0.0f; + _isActive = true; } float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) { @@ -177,29 +182,29 @@ float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) { AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { Status status = Status::IDLE; - if (_isTransiting) { + if (_isActive) { float nextTime = _currentTime + deltaTime; - if (nextTime < _preTime) { + if (nextTime < _preTransitTime) { _currentPosition = _startPosition; - status = Status::PRE_TRANSIT_IDLE; + status = Status::PRE_TRANSIT; if (_currentTime == 0) { - status = Status::START_FRAME; + status = Status::STARTED; } - } else if (nextTime < _totalTime - _postTime){ + } else if (nextTime < _totalTime - _postTransitTime){ status = Status::TRANSITING; - if (_currentTime <= _preTime) { + if (_currentTime <= _preTransitTime) { status = Status::START_TRANSIT; } else { - float percentageIntoTransit = (nextTime - _preTime) / _transitTime; + float percentageIntoTransit = (nextTime - _preTransitTime) / _transitTime; _currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine; } } else { - status = Status::POST_TRANSIT_IDLE; + status = Status::POST_TRANSIT; _currentPosition = _endPosition; if (nextTime >= _totalTime) { - _isTransiting = false; - status = Status::END_FRAME; - } else if (_currentTime < _totalTime - _postTime) { + _isActive = false; + status = Status::ENDED; + } else if (_currentTime < _totalTime - _postTransitTime) { status = Status::END_TRANSIT; } } @@ -208,11 +213,6 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { return status; } -bool AvatarTransit::getNextPosition(glm::vec3& nextPosition) { - nextPosition = _currentPosition; - return _isTransiting; -} - Avatar::Avatar(QThread* thread) : _voiceSphereID(GeometryCache::UNKNOWN_ID) { @@ -554,14 +554,11 @@ void Avatar::relayJointDataToChildren() { void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); - if (_transit.isTransiting()) { - glm::vec3 nextPosition; - if (_transit.getNextPosition(nextPosition)) { - _globalPosition = nextPosition; - _globalPositionChanged = usecTimestampNow(); - if (!hasParent()) { - setLocalPosition(nextPosition); - } + if (_transit.isActive()) { + _globalPosition = _transit.getCurrentPosition(); + _globalPositionChanged = usecTimestampNow(); + if (!hasParent()) { + setLocalPosition(_transit.getCurrentPosition()); } } @@ -575,7 +572,7 @@ void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "updateJoints"); if (inView) { Head* head = getHead(); - if (_hasNewJointData || _transit.isTransiting()) { + if (_hasNewJointData || _transit.isActive()) { _skeletonModel->getRig().copyJointsFromJointData(_jointData); glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); _skeletonModel->getRig().computeExternalPoses(rootTransform); @@ -2006,7 +2003,7 @@ void Avatar::setTransitScale(float scale) { return _transit.setScale(scale); } -void Avatar::overrideNextPackagePositionData(const glm::vec3& position) { +void Avatar::overrideNextPacketPositionData(const glm::vec3& position) { std::lock_guard lock(_transitLock); _overrideGlobalPosition = true; _globalPositionOverride = position; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index fe163b9dae..e4fd667c1f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -54,13 +54,13 @@ class AvatarTransit { public: enum Status { IDLE = 0, - START_FRAME, - PRE_TRANSIT_IDLE, + STARTED, + PRE_TRANSIT, START_TRANSIT, TRANSITING, END_TRANSIT, - POST_TRANSIT_IDLE, - END_FRAME, + POST_TRANSIT, + ENDED, ABORT_TRANSIT }; @@ -95,11 +95,9 @@ public: AvatarTransit() {}; Status update(float deltaTime, const glm::vec3& avatarPosition, const TransitConfig& config); Status getStatus() { return _status; } - bool isTransiting() { return _isTransiting; } + bool isActive() { return _isActive; } glm::vec3 getCurrentPosition() { return _currentPosition; } - bool getNextPosition(glm::vec3& nextPosition); glm::vec3 getEndPosition() { return _endPosition; } - float getTransitTime() { return _totalTime; } void setScale(float scale) { _scale = scale; } void reset(); @@ -107,7 +105,7 @@ private: Status updatePosition(float deltaTime); void start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const TransitConfig& config); float getEaseValue(AvatarTransit::EaseType type, float value); - bool _isTransiting { false }; + bool _isActive { false }; glm::vec3 _startPosition; glm::vec3 _endPosition; @@ -117,10 +115,10 @@ private: glm::vec3 _transitLine; float _totalDistance { 0.0f }; - float _preTime { 0.0f }; + float _preTransitTime { 0.0f }; float _totalTime { 0.0f }; float _transitTime { 0.0f }; - float _postTime { 0.0f }; + float _postTransitTime { 0.0f }; float _currentTime { 0.0f }; EaseType _easeType { EaseType::EASE_OUT }; Status _status { Status::IDLE }; @@ -452,7 +450,7 @@ public: AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); void setTransitScale(float scale); - void overrideNextPackagePositionData(const glm::vec3& position); + void overrideNextPacketPositionData(const glm::vec3& position); signals: void targetScaleChanged(float targetScale); From 8e272d6dd27c95a9a2a73dc14c6e7755a97d9887 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 15 Oct 2018 08:43:25 -0700 Subject: [PATCH 106/276] added the correction code for when you are misslabeled as sitting while you are standing. It will now handle a sit down correctly. Also made the first approximation of state based on the average user height. This should use the user input of their height associated with their account --- interface/src/avatar/MyAvatar.cpp | 12 ++++++++++++ interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 172fdac96c..458c3ee422 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -546,6 +546,18 @@ void MyAvatar::update(float deltaTime) { _averageUserHeightCount = 1; setIsInSittingState(false); } + } else if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setIsInSittingState(true); + } } else { _sitStandStateCount = 0; // tipping point is average height when sitting. diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 10b5510e4d..61eed672ef 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1846,7 +1846,7 @@ private: ThreadSafeValueCache _isInSittingState { false }; int _sitStandStateCount { 0 }; int _squatCount { 0 }; - float _tippingPoint { DEFAULT_FLOOR_HEIGHT }; + float _tippingPoint { DEFAULT_AVATAR_HEIGHT }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; From ae3ae9ce9a081e7b41d544f038655adda975b367 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 15 Oct 2018 09:52:34 -0700 Subject: [PATCH 107/276] put in condition for away mode so that when you take the hmd off and put it back on the initial guess to your state is made again. --- interface/src/avatar/MyAvatar.cpp | 102 +++++++++++++++++------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 458c3ee422..4e347eebbb 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -530,58 +530,70 @@ void MyAvatar::update(float deltaTime) { glm::vec3 worldHips = transformPoint(getTransform().getMatrix(), avatarHips); glm::vec3 sensorHips = transformPoint(glm::inverse(getSensorToWorldMatrix()), worldHips); + const int VELOCITY_COUNT_THRESHOLD = 60; // put update sit stand state counts here - if (!getIsSitStandStateLocked() && (_follow._velocityCount > 60)) { - if (getIsInSittingState()) { - if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we recenter upwards then no longer in sitting state - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; + if (!getIsSitStandStateLocked() && (_follow._velocityCount > VELOCITY_COUNT_THRESHOLD)) { + if (!getIsAway()) { + if (getIsInSittingState()) { + if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we recenter upwards then no longer in sitting state + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setIsInSittingState(false); } - _averageUserHeightCount = 1; - setIsInSittingState(false); - } - } else if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; + } else if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setIsInSittingState(true); } - _averageUserHeightCount = 1; - setIsInSittingState(true); + } else { + _sitStandStateCount = 0; + // tipping point is average height when sitting. + _tippingPoint = averageSensorSpaceHeight; } } else { - _sitStandStateCount = 0; - // tipping point is average height when sitting. - _tippingPoint = averageSensorSpaceHeight; + // in the standing state + if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setIsInSittingState(true); + } + } else { + // use the mode height for the tipping point when we are standing. + _tippingPoint = getCurrentStandingHeight(); + _sitStandStateCount = 0; + } } } else { - // in the standing state - if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; - } - _averageUserHeightCount = 1; - setIsInSittingState(true); - } - } else { - // use the mode height for the tipping point when we are standing. - _tippingPoint = getCurrentStandingHeight(); - _sitStandStateCount = 0; - } + // if you are away then reset the average and set state to standing. + _squatCount = 0; + _sitStandStateCount = 0; + _follow._velocityCount = 0; + _averageUserHeightCount = 1; + _sumUserHeightSensorSpace = DEFAULT_AVATAR_HEIGHT; + _tippingPoint = DEFAULT_AVATAR_HEIGHT; + setIsInSittingState(false); } } From fede22499c137785b50cfbb26023847b76b55b3b Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 15 Oct 2018 11:58:13 -0700 Subject: [PATCH 108/276] Added app to control exponential filters on vive trackers, in real time --- scripts/developer/exponentialFilterApp.js | 240 ++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 scripts/developer/exponentialFilterApp.js diff --git a/scripts/developer/exponentialFilterApp.js b/scripts/developer/exponentialFilterApp.js new file mode 100644 index 0000000000..774ea95533 --- /dev/null +++ b/scripts/developer/exponentialFilterApp.js @@ -0,0 +1,240 @@ +var LEFT_HAND_INDEX = 0; +var RIGHT_HAND_INDEX = 1; +var LEFT_FOOT_INDEX = 2; +var RIGHT_FOOT_INDEX = 3; +var HIPS_INDEX = 4; +var SPINE2_INDEX = 5; + +var HAND_SMOOTHING_TRANSLATION = 0.3; +var HAND_SMOOTHING_ROTATION = 0.15; +var FOOT_SMOOTHING_TRANSLATION = 0.3; +var FOOT_SMOOTHING_ROTATION = 0.15; +var TORSO_SMOOTHING_TRANSLATION = 0.3; +var TORSO_SMOOTHING_ROTATION = 0.16; + +var mappingJson = { + name: "com.highfidelity.testing.exponentialFilterApp", + channels: [ + { + from: "Standard.LeftHand", + to: "Actions.LeftHand", + filters: [ + { + type: "exponentialSmoothing", + translation: HAND_SMOOTHING_TRANSLATION, + rotation: HAND_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.RightHand", + to: "Actions.RightHand", + filters: [ + { + type: "exponentialSmoothing", + translation: HAND_SMOOTHING_TRANSLATION, + rotation: HAND_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.LeftFoot", + to: "Actions.LeftFoot", + filters: [ + { + type: "exponentialSmoothing", + translation: FOOT_SMOOTHING_TRANSLATION, + rotation: FOOT_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.RightFoot", + to: "Actions.RightFoot", + filters: [ + { + type: "exponentialSmoothing", + translation: FOOT_SMOOTHING_TRANSLATION, + rotation: FOOT_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.Hips", + to: "Actions.Hips", + filters: [ + { + type: "exponentialSmoothing", + translation: TORSO_SMOOTHING_TRANSLATION, + rotation: TORSO_SMOOTHING_ROTATION + } + ] + }, + { + from: "Standard.Spine2", + to: "Actions.Spine2", + filters: [ + { + type: "exponentialSmoothing", + translation: TORSO_SMOOTHING_TRANSLATION, + rotation: TORSO_SMOOTHING_ROTATION + } + ] + } + ] +}; + +// +// tablet app boiler plate +// + +var TABLET_BUTTON_NAME = "EXPFILT"; +var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/exponentialFilterApp.html?7"; + +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var tabletButton = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg", + activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg" +}); + +tabletButton.clicked.connect(function () { + if (shown) { + tablet.gotoHomeScreen(); + } else { + tablet.gotoWebScreen(HTML_URL); + } +}); + +var shown = false; + +function onScreenChanged(type, url) { + if (type === "Web" && url === HTML_URL) { + tabletButton.editProperties({isActive: true}); + if (!shown) { + // hook up to event bridge + tablet.webEventReceived.connect(onWebEventReceived); + shownChanged(true); + } + shown = true; + } else { + tabletButton.editProperties({isActive: false}); + if (shown) { + // disconnect from event bridge + tablet.webEventReceived.disconnect(onWebEventReceived); + shownChanged(false); + } + shown = false; + } +} + +function getTranslation(i) { + return mappingJson.channels[i].filters[0].translation; +} +function setTranslation(i, value) { + mappingJson.channels[i].filters[0].translation = value; + mappingChanged(); +} +function getRotation(i) { + return mappingJson.channels[i].filters[0].rotation; +} +function setRotation(i, value) { + mappingJson.channels[i].filters[0].rotation = value; mappingChanged(); +} + +function onWebEventReceived(msg) { + if (msg.name === "init-complete") { + var values = [ + {name: "enable-filtering", val: filterEnabled ? "on" : "off", checked: false}, + {name: "left-hand-translation", val: getTranslation(LEFT_HAND_INDEX), checked: false}, + {name: "left-hand-rotation", val: getRotation(LEFT_HAND_INDEX), checked: false}, + {name: "right-hand-translation", val: getTranslation(RIGHT_HAND_INDEX), checked: false}, + {name: "right-hand-rotation", val: getRotation(RIGHT_HAND_INDEX), checked: false}, + {name: "left-foot-translation", val: getTranslation(LEFT_FOOT_INDEX), checked: false}, + {name: "left-foot-rotation", val: getRotation(LEFT_FOOT_INDEX), checked: false}, + {name: "right-foot-translation", val: getTranslation(RIGHT_FOOT_INDEX), checked: false}, + {name: "right-foot-rotation", val: getRotation(RIGHT_FOOT_INDEX), checked: false}, + {name: "hips-translation", val: getTranslation(HIPS_INDEX), checked: false}, + {name: "hips-rotation", val: getRotation(HIPS_INDEX), checked: false}, + {name: "spine2-translation", val: getTranslation(SPINE2_INDEX), checked: false}, + {name: "spine2-rotation", val: getRotation(SPINE2_INDEX), checked: false} + ]; + tablet.emitScriptEvent(JSON.stringify(values)); + } else if (msg.name === "enable-filtering") { + if (msg.val === "on") { + filterEnabled = true; + } else if (msg.val === "off") { + filterEnabled = false; + } + mappingChanged(); + } else if (msg.name === "left-hand-translation") { + setTranslation(LEFT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "left-hand-rotation") { + setRotation(LEFT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "right-hand-translation") { + setTranslation(RIGHT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "right-hand-rotation") { + setRotation(RIGHT_HAND_INDEX, Number(msg.val)); + } else if (msg.name === "left-foot-translation") { + setTranslation(LEFT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "left-foot-rotation") { + setRotation(LEFT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "right-foot-translation") { + setTranslation(RIGHT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "right-foot-rotation") { + setRotation(RIGHT_FOOT_INDEX, Number(msg.val)); + } else if (msg.name === "hips-translation") { + setTranslation(HIPS_INDEX, Number(msg.val)); + } else if (msg.name === "hips-rotation") { + setRotation(HIPS_INDEX, Number(msg.val)); + } else if (msg.name === "spine2-translation") { + setTranslation(SPINE2_INDEX, Number(msg.val)); + } else if (msg.name === "spine2-rotation") { + setRotation(SPINE2_INDEX, Number(msg.val)); + } +} + +tablet.screenChanged.connect(onScreenChanged); + +function shutdownTabletApp() { + tablet.removeButton(tabletButton); + if (shown) { + tablet.webEventReceived.disconnect(onWebEventReceived); + tablet.gotoHomeScreen(); + } + tablet.screenChanged.disconnect(onScreenChanged); +} + +// +// end tablet app boiler plate +// + +var filterEnabled = true; +var mapping; +function mappingChanged() { + if (mapping) { + mapping.disable(); + } + if (filterEnabled) { + mapping = Controller.parseMapping(JSON.stringify(mappingJson)); + mapping.enable(); + } +} + +function shownChanged(newShown) { + if (newShown) { + mappingChanged(); + } else { + mapping.disable(); + } +} + +mappingChanged(); + +Script.scriptEnding.connect(function() { + if (mapping) { + mapping.disable(); + } + tablet.removeButton(tabletButton); +}); + From 640ed6a32a23b4f9b0ebe980d7952ad47359f2bb Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 15 Oct 2018 12:42:07 -0700 Subject: [PATCH 109/276] Added "Developer > Avatar > Show Tracked Objects" menu --- interface/src/Application.cpp | 46 ++++++++++--------- interface/src/Application.h | 5 ++ interface/src/Menu.cpp | 2 + interface/src/Menu.h | 1 + .../animation/src/AnimInverseKinematics.cpp | 2 +- libraries/animation/src/Rig.cpp | 2 +- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7457afe091..b05de598a0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5802,43 +5802,41 @@ void Application::update(float deltaTime) { myAvatar->setControllerPoseInSensorFrame(action, pose.transform(avatarToSensorMatrix)); } - // AJT: TODO put a nice menu around this. - // Make sure to remove all markers when menu is turned off. - { + static const std::vector trackedObjectStringLiterals = { + QStringLiteral("_TrackedObject00"), QStringLiteral("_TrackedObject01"), QStringLiteral("_TrackedObject02"), QStringLiteral("_TrackedObject03"), + QStringLiteral("_TrackedObject04"), QStringLiteral("_TrackedObject05"), QStringLiteral("_TrackedObject06"), QStringLiteral("_TrackedObject07"), + QStringLiteral("_TrackedObject08"), QStringLiteral("_TrackedObject09"), QStringLiteral("_TrackedObject10"), QStringLiteral("_TrackedObject11"), + QStringLiteral("_TrackedObject12"), QStringLiteral("_TrackedObject13"), QStringLiteral("_TrackedObject14"), QStringLiteral("_TrackedObject15") + }; + + // Controlled by the Developer > Avatar > Show Tracked Objects menu. + if (_showTrackedObjects) { static const std::vector trackedObjectActions = { - controller::Action::TRACKED_OBJECT_00, - controller::Action::TRACKED_OBJECT_01, - controller::Action::TRACKED_OBJECT_02, - controller::Action::TRACKED_OBJECT_03, - controller::Action::TRACKED_OBJECT_04, - controller::Action::TRACKED_OBJECT_05, - controller::Action::TRACKED_OBJECT_06, - controller::Action::TRACKED_OBJECT_07, - controller::Action::TRACKED_OBJECT_08, - controller::Action::TRACKED_OBJECT_09, - controller::Action::TRACKED_OBJECT_10, - controller::Action::TRACKED_OBJECT_11, - controller::Action::TRACKED_OBJECT_12, - controller::Action::TRACKED_OBJECT_13, - controller::Action::TRACKED_OBJECT_14, - controller::Action::TRACKED_OBJECT_15 + controller::Action::TRACKED_OBJECT_00, controller::Action::TRACKED_OBJECT_01, controller::Action::TRACKED_OBJECT_02, controller::Action::TRACKED_OBJECT_03, + controller::Action::TRACKED_OBJECT_04, controller::Action::TRACKED_OBJECT_05, controller::Action::TRACKED_OBJECT_06, controller::Action::TRACKED_OBJECT_07, + controller::Action::TRACKED_OBJECT_08, controller::Action::TRACKED_OBJECT_09, controller::Action::TRACKED_OBJECT_10, controller::Action::TRACKED_OBJECT_11, + controller::Action::TRACKED_OBJECT_12, controller::Action::TRACKED_OBJECT_13, controller::Action::TRACKED_OBJECT_14, controller::Action::TRACKED_OBJECT_15 }; int i = 0; glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); for (auto& action : trackedObjectActions) { - QString key = QString("_TrackedObject%1").arg(i); controller::Pose pose = userInputMapper->getPoseState(action); if (pose.valid) { glm::vec3 pos = transformPoint(myAvatarMatrix, pose.translation); glm::quat rot = glmExtractRotation(myAvatarMatrix) * pose.rotation; - DebugDraw::getInstance().addMarker(key, rot, pos, BLUE); + DebugDraw::getInstance().addMarker(trackedObjectStringLiterals[i], rot, pos, BLUE); } else { - DebugDraw::getInstance().removeMarker(key); + DebugDraw::getInstance().removeMarker(trackedObjectStringLiterals[i]); } i++; } + } else if (_prevShowTrackedObjects) { + for (auto& key : trackedObjectStringLiterals) { + DebugDraw::getInstance().removeMarker(key); + } } + _prevShowTrackedObjects = _showTrackedObjects; } updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... @@ -8330,6 +8328,10 @@ void Application::setShowBulletConstraintLimits(bool value) { _physicsEngine->setShowBulletConstraintLimits(value); } +void Application::setShowTrackedObjects(bool value) { + _showTrackedObjects = value; +} + void Application::startHMDStandBySession() { _autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 75260b910f..739738e7e8 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -496,6 +496,8 @@ private slots: void setShowBulletConstraints(bool value); void setShowBulletConstraintLimits(bool value); + void setShowTrackedObjects(bool value); + private: void init(); bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event); @@ -777,5 +779,8 @@ private: std::atomic _pendingRenderEvent { true }; bool quitWhenFinished { false }; + + bool _showTrackedObjects { false }; + bool _prevShowTrackedObjects { false }; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index eef14c873e..8340cb8d20 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -570,6 +570,8 @@ Menu::Menu() { avatar.get(), SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowTrackedObjects, 0, false, qApp, SLOT(setShowTrackedObjects(bool))); + // Developer > Hands >>> MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 031ee2561c..1e9955a760 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -183,6 +183,7 @@ namespace MenuOption { const QString RunClientScriptTests = "Run Client Script Tests"; const QString RunTimingTests = "Run Timing Tests"; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; + const QString ShowTrackedObjects = "Show Tracked Objects"; const QString SendWrongDSConnectVersion = "Send wrong DS connect version"; const QString SendWrongProtocolVersion = "Send wrong protocol version"; const QString SetHomeLocation = "Set Home Location"; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 399eaf3fab..a1809f3438 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -24,7 +24,7 @@ #include "AnimUtil.h" static const int MAX_TARGET_MARKERS = 30; -static const float JOINT_CHAIN_INTERP_TIME = 0.25f; +static const float JOINT_CHAIN_INTERP_TIME = 0.5f; static void lookupJointInfo(const AnimInverseKinematics::JointChainInfo& jointChainInfo, int indexA, int indexB, diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d076ce5029..55cfbd6901 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1666,7 +1666,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo // This should help smooth out problems with the vive tracker when the sensor is occluded. if (prevHipsEnabled && hipsEstimated != prevHipsEstimated) { // blend from a snapshot of the previous hips. - const float HIPS_BLEND_DURATION = 0.3f; + const float HIPS_BLEND_DURATION = 0.5f; _hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION); _hipsBlendHelper.setSnapshot(_previousControllerParameters.primaryControllerPoses[PrimaryControllerType_Hips]); } else if (!prevHipsEnabled) { From 738ac30f1dc880f1dd3c903554282b543176be12 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 15 Oct 2018 11:16:30 -0700 Subject: [PATCH 110/276] Update default entity properties in Create --- scripts/system/edit.js | 215 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 203 insertions(+), 12 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 27858722ec..96d3d763b0 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -42,6 +42,9 @@ var TITLE_OFFSET = 60; var CREATE_TOOLS_WIDTH = 490; var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; +var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; +var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; + var createToolsWindow = new CreateWindow( Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml", 'Create Tools', @@ -294,6 +297,187 @@ function checkEditPermissionsAndUpdate() { } } +const DEFAULT_ENTITY_PROPERTIES = { + All: { + collisionless: true, + description: "", + rotation: { x: 0, y: 0, z: 0, w: 1 }, + collidesWith: "static,dynamic,kinematic,otherAvatar", + collisionSoundURL: "", + cloneable: false, + ignoreIK: true, + canCastShadow: true, + href: "", + script: "", + serverScripts:"", + velocity: { + x: 0, + y: 0, + z: 0 + }, + damping: 0, + angularVelocity: { + x: 0, + y: 0, + z: 0 + }, + angularDamping: 0, + restitution: 0.5, + friction: 0.5, + density: 1000, + gravity: { + x: 0, + y: 0, + z: 0 + }, + acceleration: { + x: 0, + y: 0, + z: 0 + }, + dynamic: false, + }, + Shape: { + shape: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + color: { red: 0, green: 180, blue: 239 }, + }, + Text: { + text: "Text", + textColor: { red: 255, green: 255, blue: 255 }, + backgroundColor: { red: 0, green: 0, blue: 0 }, + lineHeight: 0.06, + faceCamera: false, + }, + Zone: { + flyingAllowed: true, + ghostingAllowed: true, + filter: "", + keyLightMode: "inherit", + keyLightColor: { red: 255, green: 255, blue: 255 }, + keyLight: { + intensity: 1.0, + direction: { + x: 0.0, + y: Math.PI / 4, + }, + castShadows: true + }, + ambientLightMode: "inherit", + ambientLight: { + ambientIntensity: 0.5, + ambientURL: "" + }, + hazeMode: "inherit", + haze: { + hazeRange: 1000, + hazeAltitudeEffect: false, + hazeBaseRef: 0, + hazeColor: { + red: 128, + green: 154, + blue: 129 + }, + hazeBackgroundBlend: 0, + hazeEnableGlare: false, + hazeGlareColor: { + red: 255, + green: 229, + blue: 179 + }, + }, + bloomMode: "inherit" + }, + Model: { + modelURL: "", + collisionShape: "none", + compoundShapeURL: "", + animation: { + url: "", + running: false, + allowTranslation: false, + loop: true, + hold: false, + currentFrame: 0, + firstFrame: 0, + lastFrame: 100000, + fps: 30.0, + } + }, + Image: { + dimensions: { + x: 0.5385, + y: 0.2819, + z: 0.0092 + }, + shapeType: "box", + collisionless: true, + modelURL: IMAGE_MODEL, + textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }) + }, + Web: { + sourceUrl: "https://highfidelity.com/", + dpi: 30, + }, + Particles: { + lifespan: 1.5, + maxParticles: 10, + textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + emitRate: 5.5, + emitSpeed: 0, + speedSpread: 0, + emitDimensions: { x: 0, y: 0, z: 0 }, + emitOrientation: { x: 0, y: 0, z: 0, w: 1 }, + emitterShouldTrail: true, + particleRadius: 0.25, + radiusStart: 0, + radiusFinish: 0.1, + radiusSpread: 0, + particleColor: { + red: 255, + green: 255, + blue: 255 + }, + colorSpread: { + red: 0, + green: 0, + blue: 0 + }, + alpha: 0, + alphaStart: 1, + alphaFinish: 0, + alphaSpread: 0, + emitAcceleration: { + x: 0, + y: 2.5, + z: 0 + }, + accelerationSpread: { + x: 0, + y: 0, + z: 0 + }, + particleSpin: 0, + spinStart: 0, + spinFinish: 0, + spinSpread: 0, + rotateWithEntity: false, + azimuthStart: 0, + azimuthFinish: 0, + polarStart: -Math.PI, + polarFinish: Math.PI + }, + Light: { + color: { red: 255, green: 255, blue: 255 }, + intensity: 5.0, + falloffRadius: 1.0, + isSpotlight: false, + exponent: 1.0, + cutoff: 75.0, + dimensions: { x: 20, y: 20, z: 20 }, + }, +}; + var toolBar = (function () { var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts var that = {}, @@ -303,11 +487,29 @@ var toolBar = (function () { dialogWindow = null, tablet = null; + function applyProperties(originalProperties, newProperties) { + for (var key in newProperties) { + originalProperties[key] = newProperties[key]; + } + } function createNewEntity(properties) { var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS; var position = getPositionToCreateEntity(); var entityID = null; + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All); + + var type = properties.type; + if (type == "Box" || type == "Sphere") { + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); + } else if (type == "Image") { + properties.type = "Model"; + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Image); + } else { + applyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); + } + + if (position !== null && position !== undefined) { var direction; if (Camera.mode === "entity" || Camera.mode === "independent") { @@ -712,19 +914,8 @@ var toolBar = (function () { }); addButton("newImageButton", function () { - var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; - var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; createNewEntity({ - type: "Model", - dimensions: { - x: 0.5385, - y: 0.2819, - z: 0.0092 - }, - shapeType: "box", - collisionless: true, - modelURL: IMAGE_MODEL, - textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }) + type: "Image", }); }); From 9ec999e15e84b7eda15462efbe5ffb109955f9c4 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 15 Oct 2018 16:43:03 -0700 Subject: [PATCH 111/276] added condition for start up in oculus that gets the correct starting height --- interface/src/avatar/MyAvatar.cpp | 104 +++++++++++++++++------------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4e347eebbb..c809a2d614 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -471,7 +471,9 @@ void MyAvatar::update(float deltaTime) { const float STANDING_HEIGHT_MULTIPLE = 1.2f; const float SITTING_HEIGHT_MULTIPLE = 0.833f; const float COSINE_THIRTY_DEGREES = 0.866f; - const int SITTING_COUNT_THRESHOLD = 300; + const float COSINE_TEN_DEGREES = 0.9848f; + const int SITTING_COUNT_THRESHOLD = 100; + const int STANDING_COUNT_THRESHOLD = 10; const int SQUATTY_COUNT_THRESHOLD = 600; float tau = deltaTime / HMD_FACING_TIMESCALE; @@ -530,59 +532,70 @@ void MyAvatar::update(float deltaTime) { glm::vec3 worldHips = transformPoint(getTransform().getMatrix(), avatarHips); glm::vec3 sensorHips = transformPoint(glm::inverse(getSensorToWorldMatrix()), worldHips); + glm::vec3 headUp = newHeightReading.getRotation() * glm::vec3(0.0f, 1.0f, 0.0f); + if (glm::length(headUp) > 0.0f) { + headUp = glm::normalize(headUp); + } + float angleHeadUp = glm::dot(headUp, glm::vec3(0.0f, 1.0f, 0.0f)); + const int VELOCITY_COUNT_THRESHOLD = 60; // put update sit stand state counts here - if (!getIsSitStandStateLocked() && (_follow._velocityCount > VELOCITY_COUNT_THRESHOLD)) { + if (!getIsSitStandStateLocked()) { if (!getIsAway()) { - if (getIsInSittingState()) { - if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we recenter upwards then no longer in sitting state - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; + if ((_follow._velocityCount > VELOCITY_COUNT_THRESHOLD) || (qApp->isHMDMode() && (qApp->getActiveDisplayPlugin()->getName() == "Oculus Rift"))) { + if (getIsInSittingState()) { + if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we recenter upwards then no longer in sitting state + _sitStandStateCount++; + if (_sitStandStateCount > STANDING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setIsInSittingState(false); } - _averageUserHeightCount = 1; - setIsInSittingState(false); - } - } else if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; + } else if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleHeadUp > COSINE_THIRTY_DEGREES)) { + // if we are mis labelled as sitting but we are standing in the real world this will + // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + // here we stay in sit state but reset the average height + setIsInSittingState(true); } - _averageUserHeightCount = 1; - setIsInSittingState(true); + } else { + _sitStandStateCount = 0; + // tipping point is average height when sitting. + _tippingPoint = averageSensorSpaceHeight; } } else { - _sitStandStateCount = 0; - // tipping point is average height when sitting. - _tippingPoint = averageSensorSpaceHeight; - } - } else { - // in the standing state - if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; + // in the standing state + if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleHeadUp > COSINE_THIRTY_DEGREES)) { + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateCount = 0; + _squatCount = 0; + if (newHeightReading.isValid()) { + _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; + _tippingPoint = newHeightReading.getTranslation().y; + } + _averageUserHeightCount = 1; + setIsInSittingState(true); } - _averageUserHeightCount = 1; - setIsInSittingState(true); + } else { + // use the mode height for the tipping point when we are standing. + _tippingPoint = getCurrentStandingHeight(); + _sitStandStateCount = 0; } - } else { - // use the mode height for the tipping point when we are standing. - _tippingPoint = getCurrentStandingHeight(); - _sitStandStateCount = 0; } } } else { @@ -4240,6 +4253,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } else { if ((glm::length(myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD).getVelocity()) > MINIMUM_HMD_VELOCITY)) { _velocityCount++; + qCDebug(interfaceapp) << "velocity count is " << _velocityCount << " is away " << myAvatar.getIsAway() << " hmd mode "<< qApp->isHMDMode() << " " << qApp->getActiveDisplayPlugin()->getName(); } } } else { From 49b869c5e31c7d0b45ff51eaa54af28850cd26bf Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 15 Oct 2018 18:02:50 -0700 Subject: [PATCH 112/276] got rid of velocity count, now use 'away' to trigger when to start computing the sit stand state --- interface/src/avatar/MyAvatar.cpp | 15 +-------------- interface/src/avatar/MyAvatar.h | 1 - 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c809a2d614..66e8570743 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -542,7 +542,7 @@ void MyAvatar::update(float deltaTime) { // put update sit stand state counts here if (!getIsSitStandStateLocked()) { if (!getIsAway()) { - if ((_follow._velocityCount > VELOCITY_COUNT_THRESHOLD) || (qApp->isHMDMode() && (qApp->getActiveDisplayPlugin()->getName() == "Oculus Rift"))) { + if (qApp->isHMDMode()) { if (getIsInSittingState()) { if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { // if we recenter upwards then no longer in sitting state @@ -602,7 +602,6 @@ void MyAvatar::update(float deltaTime) { // if you are away then reset the average and set state to standing. _squatCount = 0; _sitStandStateCount = 0; - _follow._velocityCount = 0; _averageUserHeightCount = 1; _sumUserHeightSensorSpace = DEFAULT_AVATAR_HEIGHT; _tippingPoint = DEFAULT_AVATAR_HEIGHT; @@ -4244,18 +4243,6 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } } - const int VELOCITY_COUNT_THRESHOLD = 60; - const float MINIMUM_HMD_VELOCITY = 0.1f; - if (_velocityCount > VELOCITY_COUNT_THRESHOLD) { - if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { - activate(Vertical); - } - } else { - if ((glm::length(myAvatar.getControllerPoseInSensorFrame(controller::Action::HEAD).getVelocity()) > MINIMUM_HMD_VELOCITY)) { - _velocityCount++; - qCDebug(interfaceapp) << "velocity count is " << _velocityCount << " is away " << myAvatar.getIsAway() << " hmd mode "<< qApp->isHMDMode() << " " << qApp->getActiveDisplayPlugin()->getName(); - } - } } else { if (!isActive(Rotation) && getForceActivateRotation()) { activate(Rotation); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 61eed672ef..e8d9090e03 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1760,7 +1760,6 @@ private: std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; std::atomic _toggleHipsFollowing { true }; - int _velocityCount { 0 }; }; FollowHelper _follow; From 7d7fe8c08913de6fc852881df268984934c2c80a Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 15 Oct 2018 18:12:27 -0700 Subject: [PATCH 113/276] removed some cruft --- interface/src/avatar/MyAvatar.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 66e8570743..00a8e0f30e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -510,6 +510,7 @@ void MyAvatar::update(float deltaTime) { setCurrentStandingHeight(computeStandingHeightMode(newHeightReading)); setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); } + float averageSensorSpaceHeight = _sumUserHeightSensorSpace / _averageUserHeightCount; // if the spine is straight and the head is below the default position by 5 cm then increment squatty count. const float SQUAT_THRESHOLD = 0.05f; @@ -526,12 +527,6 @@ void MyAvatar::update(float deltaTime) { _squatCount = 0; } - float averageSensorSpaceHeight = _sumUserHeightSensorSpace / _averageUserHeightCount; - - glm::vec3 avatarHips = getAbsoluteJointTranslationInObjectFrame(getJointIndex("Hips")); - glm::vec3 worldHips = transformPoint(getTransform().getMatrix(), avatarHips); - glm::vec3 sensorHips = transformPoint(glm::inverse(getSensorToWorldMatrix()), worldHips); - glm::vec3 headUp = newHeightReading.getRotation() * glm::vec3(0.0f, 1.0f, 0.0f); if (glm::length(headUp) > 0.0f) { headUp = glm::normalize(headUp); From 7da5fa9ea7fcba68b9c4ac46af3d558c5a0b3cba Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 16 Oct 2018 11:33:58 -0700 Subject: [PATCH 114/276] Transit animations won't play, they just get sent over the network --- interface/src/avatar/AvatarManager.cpp | 8 +- interface/src/avatar/MyAvatar.cpp | 17 ++++ interface/src/avatar/MyAvatar.h | 2 + libraries/animation/src/Rig.cpp | 120 +++++++++++++++++++++++-- libraries/animation/src/Rig.h | 8 ++ 5 files changed, 146 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c1fddaa680..373ae9980a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -151,22 +151,22 @@ void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { switch (status) { case AvatarTransit::Status::STARTED: qDebug() << "START_FRAME"; - _myAvatar->overrideAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, + _myAvatar->overrideNetworkAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, startAnimation._firstFrame + startAnimation._frameCount); break; case AvatarTransit::Status::START_TRANSIT: qDebug() << "START_TRANSIT"; - _myAvatar->overrideAnimation(middleAnimation._animationUrl, REFERENCE_FPS, false, middleAnimation._firstFrame, + _myAvatar->overrideNetworkAnimation(middleAnimation._animationUrl, REFERENCE_FPS, false, middleAnimation._firstFrame, middleAnimation._firstFrame + middleAnimation._frameCount); break; case AvatarTransit::Status::END_TRANSIT: qDebug() << "END_TRANSIT"; - _myAvatar->overrideAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, + _myAvatar->overrideNetworkAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, endAnimation._firstFrame + endAnimation._frameCount); break; case AvatarTransit::Status::ENDED: qDebug() << "END_FRAME"; - _myAvatar->restoreAnimation(); + _myAvatar->restoreNetworkAnimation(); break; case AvatarTransit::Status::PRE_TRANSIT: break; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b347963cf1..152215e717 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1124,6 +1124,15 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float _skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame); } +void MyAvatar::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "overrideNetworkAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), + Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); + return; + } + _skeletonModel->getRig().overrideNetworkAnimation(url, fps, loop, firstFrame, lastFrame); +} + void MyAvatar::restoreAnimation() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "restoreAnimation"); @@ -1132,6 +1141,14 @@ void MyAvatar::restoreAnimation() { _skeletonModel->getRig().restoreAnimation(); } +void MyAvatar::restoreNetworkAnimation() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "restoreNetworkAnimation"); + return; + } + _skeletonModel->getRig().restoreNetworkAnimation(); +} + QStringList MyAvatar::getAnimationRoles() { if (QThread::currentThread() != thread()) { QStringList result; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 16b765711a..9770a5bb1a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -376,6 +376,7 @@ public: * }, 3000); */ Q_INVOKABLE void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + Q_INVOKABLE void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); /**jsdoc * The avatar animation system includes a set of default animations along with rules for how those animations are blended together with @@ -392,6 +393,7 @@ public: * }, 3000); */ Q_INVOKABLE void restoreAnimation(); + Q_INVOKABLE void restoreNetworkAnimation(); /**jsdoc * Each avatar has an avatar-animation.json file that defines which animations are used and how they are blended together with procedural data diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 91d4e0f9d3..b9e654964a 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -133,6 +133,43 @@ void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firs _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B); } +void Rig::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { + UserAnimState::ClipNodeEnum clipNodeEnum; + if (_networkAnimState.clipNodeEnum == UserAnimState::None || _networkAnimState.clipNodeEnum == UserAnimState::B) { + clipNodeEnum = UserAnimState::A; + } + else { + clipNodeEnum = UserAnimState::B; + } + if (_networkNode) { + // find an unused AnimClip clipNode + _sendNetworkNode = true; + std::shared_ptr clip; + if (clipNodeEnum == UserAnimState::A) { + clip = std::dynamic_pointer_cast(_networkNode->findByName("userAnimA")); + } + else { + clip = std::dynamic_pointer_cast(_networkNode->findByName("userAnimB")); + } + if (clip) { + // set parameters + clip->setLoopFlag(loop); + clip->setStartFrame(firstFrame); + clip->setEndFrame(lastFrame); + const float REFERENCE_FRAMES_PER_SECOND = 30.0f; + float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; + clip->setTimeScale(timeScale); + clip->loadURL(url); + } + } + // store current user anim state. + _networkAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame }; + // notify the userAnimStateMachine the desired state. + _networkVars.set("userAnimNone", false); + _networkVars.set("userAnimA", clipNodeEnum == UserAnimState::A); + _networkVars.set("userAnimB", clipNodeEnum == UserAnimState::B); +} + void Rig::restoreAnimation() { if (_userAnimState.clipNodeEnum != UserAnimState::None) { _userAnimState.clipNodeEnum = UserAnimState::None; @@ -144,6 +181,17 @@ void Rig::restoreAnimation() { } } +void Rig::restoreNetworkAnimation() { + _sendNetworkNode = false; + if (_networkAnimState.clipNodeEnum != UserAnimState::None) { + _networkAnimState.clipNodeEnum = UserAnimState::None; + // notify the userAnimStateMachine the desired state. + _networkVars.set("userAnimNone", true); + _networkVars.set("userAnimA", false); + _networkVars.set("userAnimB", false); + } +} + QStringList Rig::getAnimationRoles() const { if (_animNode) { QStringList list; @@ -208,11 +256,17 @@ void Rig::restoreRoleAnimation(const QString& role) { void Rig::destroyAnimGraph() { _animSkeleton.reset(); _animLoader.reset(); + _networkLoader.reset(); _animNode.reset(); _internalPoseSet._relativePoses.clear(); _internalPoseSet._absolutePoses.clear(); _internalPoseSet._overridePoses.clear(); _internalPoseSet._overrideFlags.clear(); + _networkNode.reset(); + _networkPoseSet._relativePoses.clear(); + _networkPoseSet._absolutePoses.clear(); + _networkPoseSet._overridePoses.clear(); + _networkPoseSet._overrideFlags.clear(); _numOverrides = 0; _leftEyeJointChildren.clear(); _rightEyeJointChildren.clear(); @@ -229,14 +283,24 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff _internalPoseSet._relativePoses.clear(); _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + _networkPoseSet._relativePoses.clear(); + _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); _internalPoseSet._overridePoses.clear(); _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); _internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + + _networkPoseSet._overridePoses.clear(); + _networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); + + _networkPoseSet._overrideFlags.clear(); + _networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _numOverrides = 0; buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); @@ -270,6 +334,18 @@ void Rig::reset(const FBXGeometry& geometry) { _internalPoseSet._overrideFlags.clear(); _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + + _networkPoseSet._relativePoses.clear(); + _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); + + _networkPoseSet._overridePoses.clear(); + _networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); + + _networkPoseSet._overrideFlags.clear(); + _networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false); + _numOverrides = 0; buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); @@ -1049,26 +1125,37 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons updateAnimationStateHandlers(); _animVars.setRigToGeometryTransform(_rigToGeometryTransform); - + if (_networkNode) { + _networkVars.setRigToGeometryTransform(_rigToGeometryTransform); + } AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains, getGeometryToRigTransform(), rigToWorldTransform); // evaluate the animation AnimVariantMap triggersOut; - + AnimVariantMap networkTriggersOut; _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); + if (_networkNode) { + _networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut); + } if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { // animations haven't fully loaded yet. _internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); } + if ((int)_networkPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { + // animations haven't fully loaded yet. + _networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses(); + } _lastAnimVars = _animVars; _animVars.clearTriggers(); _animVars = triggersOut; + _networkVars.clearTriggers(); + _networkVars = networkTriggersOut; _lastContext = context; } applyOverridePoses(); buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses); - + buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses); // copy internal poses to external poses { QWriteLocker writeLock(&_externalPoseSetLock); @@ -1707,9 +1794,12 @@ void Rig::initAnimGraph(const QUrl& url) { _animGraphURL = url; _animNode.reset(); + _networkNode.reset(); // load the anim graph _animLoader.reset(new AnimNodeLoader(url)); + _networkLoader.reset(new AnimNodeLoader(url)); + std::weak_ptr weakSkeletonPtr = _animSkeleton; connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { _animNode = nodeIn; @@ -1740,6 +1830,26 @@ void Rig::initAnimGraph(const QUrl& url) { connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; }); + + connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { + _networkNode = nodeIn; + // abort load if the previous skeleton was deleted. + auto sharedSkeletonPtr = weakSkeletonPtr.lock(); + if (!sharedSkeletonPtr) { + return; + } + _networkNode->setSkeleton(sharedSkeletonPtr); + if (_networkAnimState.clipNodeEnum != UserAnimState::None) { + // restore the user animation we had before reset. + UserAnimState origState = _networkAnimState; + _networkAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; + overrideNetworkAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame); + } + // emit onLoadComplete(); + }); + connect(_networkLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { + qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; + }); } } @@ -1817,13 +1927,13 @@ void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { if (isIndexValid(i)) { // rotations are in absolute rig frame. glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot(); - data.rotation = _internalPoseSet._absolutePoses[i].rot(); + data.rotation = !_sendNetworkNode ? _internalPoseSet._absolutePoses[i].rot() : _networkPoseSet._absolutePoses[i].rot(); data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot); // translations are in relative frame but scaled so that they are in meters, // instead of geometry units. glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans(); - data.translation = _geometryOffset.scale() * _internalPoseSet._relativePoses[i].trans(); + data.translation = _geometryOffset.scale() * (!_sendNetworkNode ? _internalPoseSet._relativePoses[i].trans() : _networkPoseSet._relativePoses[i].trans()); data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans); } else { data.translationIsDefaultPose = true; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 48f00d4e5d..e1012df029 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -113,7 +113,9 @@ public: void destroyAnimGraph(); void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreAnimation(); + void restoreNetworkAnimation(); QStringList getAnimationRoles() const; void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreRoleAnimation(const QString& role); @@ -270,6 +272,7 @@ protected: // Only accessed by the main thread PoseSet _internalPoseSet; + PoseSet _networkPoseSet; // Copy of the _poseSet for external threads. PoseSet _externalPoseSet; @@ -301,9 +304,12 @@ protected: QUrl _animGraphURL; std::shared_ptr _animNode; + std::shared_ptr _networkNode; std::shared_ptr _animSkeleton; std::unique_ptr _animLoader; + std::unique_ptr _networkLoader; AnimVariantMap _animVars; + AnimVariantMap _networkVars; enum class RigRole { Idle = 0, @@ -350,6 +356,7 @@ protected: }; UserAnimState _userAnimState; + UserAnimState _networkAnimState; std::map _roleAnimStates; float _leftHandOverlayAlpha { 0.0f }; @@ -391,6 +398,7 @@ protected: int _rigId; bool _headEnabled { false }; + bool _sendNetworkNode { false }; AnimContext _lastContext; AnimVariantMap _lastAnimVars; From 50becb5c378e80da26740a2b4702923d71481c37 Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 16 Oct 2018 12:39:34 -0700 Subject: [PATCH 115/276] merge particle explorer into properties, resurrect sliders, other tidying/fixes --- .../resources/qml/hifi/tablet/EditTabView.qml | 20 +- .../qml/hifi/tablet/EditToolsTabView.qml | 23 +- scripts/system/edit.js | 77 +- scripts/system/html/css/edit-style.css | 68 +- scripts/system/html/entityProperties.html | 1 + scripts/system/html/js/entityProperties.js | 1074 ++++++++++++----- scripts/system/html/js/underscore-min.js | 6 + 7 files changed, 863 insertions(+), 406 deletions(-) create mode 100644 scripts/system/html/js/underscore-min.js diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index 4ac8755570..bf7dd3e66b 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -175,7 +175,7 @@ TabBar { method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } }); - editTabView.currentIndex = 4 + editTabView.currentIndex = 2 } } @@ -279,21 +279,6 @@ TabBar { } } - EditTabButton { - title: "P" - active: true - enabled: true - property string originalUrl: "" - - property Component visualItem: Component { - WebView { - id: particleExplorerWebView - url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" - enabled: true - } - } - } - function fromScript(message) { switch (message.method) { case 'selectTab': @@ -326,9 +311,6 @@ TabBar { case 'grid': editTabView.currentIndex = 3; break; - case 'particle': - editTabView.currentIndex = 4; - break; default: console.warn('Attempt to switch to invalid tab:', id); } diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml index 00084b8ca9..13b1caf8fb 100644 --- a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml @@ -18,7 +18,6 @@ TabBar { readonly property int create: 0 readonly property int properties: 1 readonly property int grid: 2 - readonly property int particle: 3 } readonly property HifiConstants hifi: HifiConstants {} @@ -182,7 +181,7 @@ TabBar { method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } }); - editTabView.currentIndex = tabIndex.particle + editTabView.currentIndex = tabIndex.properties } } @@ -271,21 +270,6 @@ TabBar { } } - EditTabButton { - title: "P" - active: true - enabled: true - property string originalUrl: "" - - property Component visualItem: Component { - WebView { - id: particleExplorerWebView - url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" - enabled: true - } - } - } - function fromScript(message) { switch (message.method) { case 'selectTab': @@ -299,7 +283,7 @@ TabBar { // Changes the current tab based on tab index or title as input function selectTab(id) { if (typeof id === 'number') { - if (id >= tabIndex.create && id <= tabIndex.particle) { + if (id >= tabIndex.create && id <= tabIndex.grid) { editTabView.currentIndex = id; } else { console.warn('Attempt to switch to invalid tab:', id); @@ -315,9 +299,6 @@ TabBar { case 'grid': editTabView.currentIndex = tabIndex.grid; break; - case 'particle': - editTabView.currentIndex = tabIndex.particle; - break; default: console.warn('Attempt to switch to invalid tab:', id); } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 27858722ec..bcfc831d57 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -12,7 +12,7 @@ /* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, - progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool, OverlaySystemWindow */ + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow */ (function() { // BEGIN LOCAL_SCOPE @@ -32,7 +32,6 @@ Script.include([ "libraries/gridTool.js", "libraries/entityList.js", "libraries/utils.js", - "particle_explorer/particleExplorerTool.js", "libraries/entityIconOverlayManager.js" ]); @@ -109,28 +108,6 @@ var entityListTool = new EntityListTool(shouldUseEditTabletApp); selectionManager.addEventListener(function () { selectionDisplay.updateHandles(); entityIconOverlayManager.updatePositions(); - - // Update particle explorer - var needToDestroyParticleExplorer = false; - if (selectionManager.selections.length === 1) { - var selectedEntityID = selectionManager.selections[0]; - if (selectedEntityID === selectedParticleEntityID) { - return; - } - var type = Entities.getEntityProperties(selectedEntityID, "type").type; - if (type === "ParticleEffect") { - selectParticleEntity(selectedEntityID); - } else { - needToDestroyParticleExplorer = true; - } - } else { - needToDestroyParticleExplorer = true; - } - - if (needToDestroyParticleExplorer && selectedParticleEntityID !== null) { - selectedParticleEntityID = null; - particleExplorerTool.destroyWebView(); - } }); var KEY_P = 80; //Key code for letter p used for Parenting hotkey. @@ -359,10 +336,6 @@ var toolBar = (function () { properties: properties }], [], true); - if (properties.type === "ParticleEffect") { - selectParticleEntity(entityID); - } - var POST_ADJUST_ENTITY_TYPES = ["Model"]; if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box after it has been created and auto-resized. @@ -1178,13 +1151,6 @@ function mouseClickEvent(event) { orientation = MyAvatar.orientation; intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); - if (event.isShifted) { - particleExplorerTool.destroyWebView(); - } - if (properties.type !== "ParticleEffect") { - particleExplorerTool.destroyWebView(); - } - if (!event.isShifted) { selectionManager.setSelections([foundEntity]); } else { @@ -1604,8 +1570,6 @@ function deleteSelectedEntities() { if (SelectionManager.hasSelection()) { var deletedIDs = []; - selectedParticleEntityID = null; - particleExplorerTool.destroyWebView(); SelectionManager.saveProperties(); var savedProperties = []; var newSortedSelection = sortSelectedEntities(selectionManager.selections); @@ -2572,31 +2536,6 @@ propertyMenu.onSelectMenuItem = function (name) { var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); var propertiesTool = new PropertiesTool(); -var particleExplorerTool = new ParticleExplorerTool(createToolsWindow); -var selectedParticleEntityID = null; - -function selectParticleEntity(entityID) { - selectedParticleEntityID = entityID; - - var properties = Entities.getEntityProperties(entityID); - if (properties.emitOrientation) { - properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); - } - - particleExplorerTool.destroyWebView(); - particleExplorerTool.createWebView(); - - particleExplorerTool.setActiveParticleEntity(entityID); - - // Switch to particle explorer - var selectTabMethod = { method: 'selectTab', params: { id: 'particle' } }; - if (shouldUseEditTabletApp()) { - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.sendToQml(selectTabMethod); - } else { - createToolsWindow.sendToQml(selectTabMethod); - } -} entityListTool.webView.webEventReceived.connect(function(data) { try { @@ -2610,20 +2549,6 @@ entityListTool.webView.webEventReceived.connect(function(data) { parentSelectedEntities(); } else if (data.type === 'unparent') { unparentSelectedEntities(); - } else if (data.type === "selectionUpdate") { - var ids = data.entityIds; - if (ids.length === 1) { - if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") { - if (JSON.stringify(selectedParticleEntityID) === JSON.stringify(ids[0])) { - // This particle entity is already selected, so return - return; - } - // Destroy the old particles web view first - } else { - selectedParticleEntityID = 0; - particleExplorerTool.destroyWebView(); - } - } } }); diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index f69ace2401..8d334609a6 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -300,6 +300,28 @@ input[type=number].hover-down::-webkit-inner-spin-button:after { color: #ffffff; } +input[type=range] { + -webkit-appearance: none; + background: #2e2e2e; + height: 1.8rem; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb { + -webkit-appearance:none; + width: 0.6rem; + height: 1.8rem; + padding:0; + margin: 0; + background-color: #696969; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb:hover { + background-color: white; +} +input[type=range]:focus { /*#252525*/ + outline: none; +} + input.no-spin::-webkit-outer-spin-button, input.no-spin::-webkit-inner-spin-button { display: none; @@ -623,6 +645,15 @@ hr { margin-left: 10px; } +.property.range label{ + display: block; +} +.property.range input[type=number]{ + margin-left: 0.8rem; + width: 5.4rem; + height: 1.8rem; +} + .text label, .url label, .number label, .textarea label, .xy label, .wh label, .rgb label, .xyz label,.pyr label, .dropdown label, .gen label { float: left; margin-left: 1px; @@ -853,13 +884,13 @@ div.refresh input[type="button"] { color: #1080b8; } -.tuple .red:focus, .tuple .x:focus, .tuple .pitch:focus { +.tuple .red:focus, .tuple .x:focus, .tuple .pitch:focus, .tuple .width:focus { outline-color: #e2334d; } -.tuple .green:focus, .tuple .y:focus, .tuple .yaw:focus { +.tuple .green:focus, .tuple .y:focus, .tuple .yaw:focus, .tuple .height:focus { outline-color: #1ac567; } -tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { +.tuple .blue:focus, .tuple .z:focus, .tuple .roll:focus { outline-color: #1080b8; } @@ -884,6 +915,37 @@ tuple, .blue:focus, .tuple .z:focus, .tuple .roll:focus { float: left; } +.property.texture { + display: block; +} +.property.texture input{ + margin: 0.4rem 0; +} +.texture-image img{ + padding: 0; + margin: 0; + width: 100%; + height: 100%; + display: none; +} +.texture-image { + display: block; + position: relative; + background-repeat: no-repeat; + background-position: center; + background-size: 100% 100%; + margin-top: 0.4rem; + height:128px; + width: 128px; + background-image: url('data:image/png;base64,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'); +} +.texture-image.no-texture { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAB81JREFUeNrsnTGPm0oXht97FWm2Ch2pTEeHpUihsyvTuXO67Ta/IPkr+Qfp3MWdO7Zad0SKZDo6XIWOrTzV9xVXZ8SygGHXG4/t96lW68GGw8vMmZlzDv98+/btfyBXy780wXXzTv74/fs3rXFFfPz4kT0AoQAoAJqAAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQCgAQgEQCoBQAIQCIBQAoQAIBUAoAHLmvDv3C7i7u4PjOMiyDOv1+mC75XKJoiga2wRBAN/34TgOHMdBWZYoigJpmiLPcwrARhzHAQD4vg/P81pvlLRrwvM8zGYz00ZrbY5xHAe+7yPPc9zf36MsSwrAVmazGX78+DHoGM/zsFgsAAB5nmOz2ZgeQimF8XiMMAxNu+VyaQRCH8Ai8jyH4zgIw7D3MUopzOdzAECaplitVk+GB601kiTBz58/obWG4ziIoohOoI38+vULABCGYWd3X2U6nUIphbIsEcdxa7uiKPDw8GCGGtd1KQDbKMsSWZZBKYXJZNLrGN/3zdN/iDRNTdcvx1EAFqGUwmazeeIQduG6LpRSAIAsy3r9hrRjD2BxL5AkiXEI+8wetNa9PXtp13eIoQBOQJIkxmHrcgjlJkov8JKpJwVgIVpr47CFYdh6g/f7/ZM5/9CehgKwmDRNURQFlFKYTqeNN/rx8dH0AH2faBn7KYAzQKZ1QRCYZd0qf/78MX+PRqNe3ymO5W63owBsR9bwZShoGirEq++zeBQEweBZAwVwYh4eHqC1RhAErQ6jOHVdK3yu65qhJE1TDgHn5BDKTW6auxdFYdYOgiDAYrF40k4phTAM8fnzZyilUBRF54rhOfIOF06SJMYPaPt8v99jOp3C8zx4nget9bPZQ5ZlF3fzL0IAZVke9OLv7+/Njl/brCHLMozHY4xGI3z48MH0EEVRIMuyi40H+EdqBbNS6HXBSqGEAiAUAAVAE1AAhAIgFAChAAgFQCgAQgGQq+Eom0GLxeJgGHYVSdCUhM02yrI0qV5hGGIymaAsy9b0LNd1cXt7CwDYbDa98wOA/zKLVquVSQGr/nYTbe2iKDIh53JtZVmiLEvsdjtst9tn5z7EDmfXA3QFXdaTMbvYbrdm568tgkdueJ7njbt3QwJA+8YJ1tsFQQDXdXFzc2N2E0Uwk8kEX758eXbMEDtY2QOsVqtn//v69SsAYL1eH9xK7dNGgjuiKMJ4PH4WmSN7+QBMFu/3798bn1oAzz47NvVrqmYgz2azRpv1scNV+wDVaN969y6JIEmSWBmyJenlIgZbcgvOzgmUqJxqkmY18ldCvGwkz/MntQcogBcgETrVMV98Aptvfh1JTKEAXsBms4HWGp7nYT6fw3Ec5Hlufbi253lQSkFr3VqmhgLoQVmW2G63ZigQx8/2my/FKCR17WLWAV7LfD5vzOFLkqS1W0/T1HT9RVFY5/jNZjMz3ouvorVGHMet9QheYoer7AGq478Y2LaiDTc3N3Bd90megSwG2YQVPcDQ+a/ccK01ttutWSWsetl/i7bfq16TzP1lGFgul0exw9X2AJLGJV3joRXCl3rnXbUDhmQKl2WJ9XoNrbV1vdXZCUCWWqvVQGR8HFIgqmuaKUiCSJcA+nrzWmvzdA/ZN6EAKlTz/eXmA3iSuXOoNEzfBRsA+PTpU+PnUjxSfnvo9/ZNR6cAakjFj2rqd3VtQJ6u1z5h1e+SdYbqdK5aWHLImC0OoFQgpRN4YPoD/LfRVC8C2TQlkhVC3/dfVDG0/l1xHCOKIvi+b572atJoURSdtYnbfAHxV0aj0TP/oY8dzqYH6OscHXK26tO+rqcujmNTIKqtJkDfc0vTFMvl8smu436/R57niOO4NSbh0HfLkFHtpYbY4dgwOfRKYXIooQAIBUAB0AQUAKEACAVAKABCARAKgFAA5Gp4s93AKIrw/v17ExsnFEWB/X6P3W6HLMtaN0+GJkwOad+W2FlPLq3GHFSRdq85h2PYyGoByG6cvJOnHiEryZJSg7e+s1ZNmOyzSza0ffWYJsIwbMzk7Tp+6Dm81kZWC0BoCnSU7dowDE2K12q1alT60EDJYwVWKqUQRdHgPf9jnfMQG52dDyA5fLKnLlGztiB5Bn1eP3fuNvr31IaWZM9jhHIdEwk5G1Jk4hxtdPJZQJZlJrLWlnBpx3FMmrnrup3RReduIyumgXJxtryRUxw4mQXIO4Yv0UZWCMDWN3I2vX7u0mxk1RtDmp6yoQmTbe27kjK7iOMYt7e3CIIA2+22VyLIWyZ5Hrsnsmol0Jac+fo51QtSXJKNrOgBuvLsTrUOUO8FxAP3ff/gTXiLc3irt5aevAdQSpmpja0vZqq+fm4ymfz18i5vaaOTC0DSvapv8rQRmRY6joPxeHwxNjqpAGSpUwx8ikKJQ5AyNFKb4BJsdBIfwPM8BEFgFjXSNG3debMJSUv7GyuWf8tGby6Aaq2c+qvaJce/a3p2ioTJQ73A3d3di6aBbef8WhtZKQDJ6K1fTJ7neHx8PFjWTcbbvvPePm8QbVtc6ft/+UwKUdfbDT3n19roGDA59EphciihAAgFQAHQBBQAoQAIBUAoAEIBEAqAUACEAiAUAKEACAVAKABCARAKgFAAhAIgFAChAAgFQC4CkxgiceKEPQC5Iv4/APgB2O7x8IXXAAAAAElFTkSuQmCC'); +} +.texture-image.no-preview { + background-image: url('data:image/png;base64,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'); +} + .two-column { display: table; width: 100%; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 93f80e19b3..370681339e 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -20,6 +20,7 @@ + diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 1d8a397f07..328a998dda 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -58,6 +58,11 @@ const GROUPS = [ type: "string", propertyID: "parentID", }, + { + label: "Parent Joint Index", + type: "number", + propertyID: "parentJointIndex", + }, { label: "Locked", glyph: "", @@ -116,7 +121,7 @@ const GROUPS = [ type: "number", min: 0, step: 0.005, - fixedDecimals: 4, + decimals: 4, unit: "m", propertyID: "lineHeight" }, @@ -165,14 +170,14 @@ const GROUPS = [ min: 0, max: 10, step: 0.1, - fixedDecimals: 2, + decimals: 2, propertyID: "keyLight.intensity", showPropertyRule: { "keyLightMode": "enabled" }, }, { label: "Light Altitude", type: "number", - fixedDecimals: 2, + decimals: 2, unit: "deg", propertyID: "keyLight.direction.y", showPropertyRule: { "keyLightMode": "enabled" }, @@ -180,7 +185,7 @@ const GROUPS = [ { label: "Light Azimuth", type: "number", - fixedDecimals: 2, + decimals: 2, unit: "deg", propertyID: "keyLight.direction.x", showPropertyRule: { "keyLightMode": "enabled" }, @@ -228,7 +233,7 @@ const GROUPS = [ min: 0, max: 10, step: 0.1, - fixedDecimals: 2, + decimals: 2, propertyID: "ambientLight.ambientIntensity", showPropertyRule: { "ambientLightMode": "enabled" }, }, @@ -250,7 +255,7 @@ const GROUPS = [ min: 5, max: 10000, step: 5, - fixedDecimals: 0, + decimals: 0, unit: "m", propertyID: "haze.hazeRange", showPropertyRule: { "hazeMode": "enabled" }, @@ -267,7 +272,7 @@ const GROUPS = [ min: -1000, max: 1000, step: 10, - fixedDecimals: 0, + decimals: 0, unit: "m", propertyID: "haze.hazeBaseRef", showPropertyRule: { "hazeMode": "enabled" }, @@ -278,7 +283,7 @@ const GROUPS = [ min: -1000, max: 5000, step: 10, - fixedDecimals: 0, + decimals: 0, unit: "m", propertyID: "haze.hazeCeiling", showPropertyRule: { "hazeMode": "enabled" }, @@ -291,11 +296,11 @@ const GROUPS = [ }, { label: "Background Blend", - type: "number", + type: "slider", min: 0.0, max: 1.0, step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "haze.hazeBackgroundBlend", showPropertyRule: { "hazeMode": "enabled" }, }, @@ -313,11 +318,11 @@ const GROUPS = [ }, { label: "Glare Angle", - type: "number", + type: "slider", min: 0, max: 180, step: 1, - fixedDecimals: 0, + decimals: 0, propertyID: "haze.hazeGlareAngle", showPropertyRule: { "hazeMode": "enabled" }, }, @@ -329,31 +334,31 @@ const GROUPS = [ }, { label: "Bloom Intensity", - type: "number", + type: "slider", min: 0, max: 1, step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "bloom.bloomIntensity", showPropertyRule: { "bloomMode": "enabled" }, }, { label: "Bloom Threshold", - type: "number", + type: "slider", min: 0, min: 1, step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "bloom.bloomThreshold", showPropertyRule: { "bloomMode": "enabled" }, }, { label: "Bloom Size", - type: "number", + type: "slider", min: 0, min: 2, step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "bloom.bloomSize", showPropertyRule: { "bloomMode": "enabled" }, }, @@ -469,7 +474,6 @@ const GROUPS = [ { id: "light", addToGroup: "base", - addToGroup: "base", properties: [ { label: "Light Color", @@ -482,7 +486,7 @@ const GROUPS = [ type: "number", min: 0, step: 0.1, - fixedDecimals: 1, + decimals: 1, propertyID: "intensity", }, { @@ -490,7 +494,7 @@ const GROUPS = [ type: "number", min: 0, step: 0.1, - fixedDecimals: 1, + decimals: 1, unit: "m", propertyID: "falloffRadius", }, @@ -503,14 +507,14 @@ const GROUPS = [ label: "Spotlight Exponent", type: "number", step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "exponent", }, { label: "Spotlight Cut-Off", type: "number", step: 0.01, - fixedDecimals: 2, + decimals: 2, propertyID: "cutoff", }, ] @@ -558,19 +562,21 @@ const GROUPS = [ { label: "Material Position", type: "vec2", + vec2Type: "xy", min: 0, min: 1, step: 0.1, - vec2Type: "xy", + decimals: 4, subLabels: [ "x", "y" ], propertyID: "materialMappingPos", }, { label: "Material Scale", type: "vec2", + vec2Type: "wh", min: 0, step: 0.1, - vec2Type: "wh", + decimals: 4, subLabels: [ "width", "height" ], propertyID: "materialMappingScale", }, @@ -578,12 +584,326 @@ const GROUPS = [ label: "Material Rotation", type: "number", step: 0.1, - fixedDecimals: 2, + decimals: 2, unit: "deg", propertyID: "materialMappingRot", }, ] }, + { + id: "particles", + addToGroup: "base", + properties: [ + { + label: "Emit", + type: "bool", + propertyID: "isEmitting", + }, + { + label: "Lifespan", + type: "slider", + unit: "s", + min: 0.01, + max: 10, + step: 0.01, + propertyID: "lifespan", + }, + { + label: "Max Particles", + type: "slider", + min: 1, + max: 10000, + step: 1, + propertyID: "maxParticles", + }, + { + label: "Texture", + type: "texture", + propertyID: "particleTextures", + propertyName: "textures", // actual entity property name + }, + ] + }, + { + id: "particles_emit", + label: "EMIT", + properties: [ + { + label: "Emit Rate", + type: "slider", + min: 1, + max: 1000, + step: 1, + propertyID: "emitRate", + }, + { + label: "Emit Speed", + type: "slider", + min: 0, + max: 5, + step: 0.01, + propertyID: "emitSpeed", + }, + { + label: "Speed Spread", + type: "slider", + min: 0, + max: 5, + step: 0.01, + propertyID: "speedSpread", + }, + { + label: "Emit Dimension", + type: "vec3", + vec3Type: "xyz", + min: 0, + step: 0.01, + subLabels: [ "x", "y", "z" ], + propertyID: "emitDimensions", + }, + { + label: "Emit Orientation", + type: "vec3", + vec3Type: "pyr", + min: 0, + step: 0.01, + subLabels: [ "pitch", "yaw", "roll" ], + unit: "deg", + propertyID: "emitOrientation", + }, + { + label: "Trails", + type: "bool", + propertyID: "emitterShouldTrail", + }, + ] + }, + { + id: "particles_size", + label: "SIZE", + properties: [ + { + label: "Size", + type: "slider", + max: 4, + step: 0.01, + propertyID: "particleRadius", + }, + { + label: "Size Spread", + type: "slider", + max: 4, + step: 0.01, + propertyID: "radiusSpread", + }, + { + label: "Size Start", + type: "slider", + max: 4, + step: 0.01, + propertyID: "radiusStart", + fallbackProperty: "particleRadius", + }, + { + label: "Size Finish", + type: "slider", + max: 4, + step: 0.01, + propertyID: "radiusFinish", + fallbackProperty: "particleRadius", + }, + ] + }, + { + id: "particles_color", + label: "COLOR", + properties: [ + { + label: "Color", + type: "color", + propertyID: "particleColor", + propertyName: "color", // actual entity property name + }, + { + label: "Color Start", + type: "color", + propertyID: "colorStart", + fallbackProperty: "color", + }, + { + label: "Color Finish", + type: "color", + propertyID: "colorFinish", + fallbackProperty: "color", + }, + { + label: "Color Spread", + type: "color", + propertyID: "colorSpread", + }, + ] + }, + { + id: "particles_alpha", + label: "ALPHA", + properties: [ + { + label: "Alpha", + type: "slider", + min: 0, + max: 1, + step: 0.01, + propertyID: "alpha", + }, + { + label: "Alpha Spread", + type: "slider", + min: 0, + max: 1, + step: 0.01, + propertyID: "alphaSpread", + }, + { + label: "Alpha Start", + type: "slider", + min: 0, + max: 1, + step: 0.01, + propertyID: "alphaStart", + fallbackProperty: "alpha", + }, + { + label: "Alpha Finish", + type: "slider", + min: 0, + max: 1, + step: 0.01, + propertyID: "alphaFinish", + fallbackProperty: "alpha", + }, + ] + }, + { + id: "particles_acceleration", + label: "ACCELERATION", + properties: [ + { + label: "Emit Acceleration", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + subLabels: [ "x", "y", "z" ], + propertyID: "emitAcceleration", + }, + { + label: "Acceleration Spread", + type: "vec3", + vec3Type: "xyz", + step: 0.01, + subLabels: [ "x", "y", "z" ], + propertyID: "accelerationSpread", + }, + ] + }, + { + id: "particles_spin", + label: "SPIN", + properties: [ + { + label: "Spin", + type: "slider", + min: -360, + max: 360, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "particleSpin", + }, + { + label: "Spin Spread", + type: "slider", + min: 0, + max: 360, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinSpread", + }, + { + label: "Spin Start", + type: "slider", + min: -360, + max: 360, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinStart", + fallbackProperty: "particleSpin", + }, + { + label: "Spin Finish", + type: "slider", + min: -360, + max: 360, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinFinish", + fallbackProperty: "particleSpin", + }, + { + label: "Rotate with Entity", + type: "bool", + propertyID: "rotateWithEntity", + }, + ] + }, + { + id: "particles_constraints", + label: "CONSTRAINTS", + properties: [ + { + label: "Horizontal Angle Start", + type: "slider", + min: 0, + max: 180, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthStart", + }, + { + label: "Horizontal Angle Finish", + type: "slider", + min: -180, + max: 0, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "azimuthFinish", + }, + { + label: "Verical Angle Start", + type: "slider", + min: 0, + max: 180, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarStart", + }, + { + label: "Verical Angle Finish", + type: "slider", + min: 0, + max: 180, + step: 1, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "polarFinish", + }, + ] + }, { id: "spatial", label: "SPATIAL", @@ -592,6 +912,7 @@ const GROUPS = [ label: "Position", type: "vec3", vec3Type: "xyz", + decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m", propertyID: "position", @@ -599,8 +920,9 @@ const GROUPS = [ { label: "Rotation", type: "vec3", - step: 0.1, vec3Type: "pyr", + step: 0.1, + decimals: 4, subLabels: [ "pitch", "yaw", "roll" ], unit: "deg", propertyID: "rotation", @@ -608,8 +930,9 @@ const GROUPS = [ { label: "Dimension", type: "vec3", - step: 0.1, vec3Type: "xyz", + step: 0.1, + decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m", propertyID: "dimensions", @@ -626,8 +949,9 @@ const GROUPS = [ { label: "Pivot", type: "vec3", - step: 0.1, vec3Type: "xyz", + step: 0.1, + decimals: 4, subLabels: [ "x", "y", "z" ], unit: "(ratio of dimension)", propertyID: "registrationPoint", @@ -821,6 +1145,7 @@ const GROUPS = [ label: "Linear Velocity", type: "vec3", vec3Type: "xyz", + decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m/s", propertyID: "velocity", @@ -828,14 +1153,15 @@ const GROUPS = [ { label: "Linear Damping", type: "number", - fixedDecimals: 2, + decimals: 2, propertyID: "damping", }, { label: "Angular Velocity", type: "vec3", - multiplier: DEGREES_TO_RADIANS, vec3Type: "pyr", + multiplier: DEGREES_TO_RADIANS, + decimals: 4, subLabels: [ "pitch", "yaw", "roll" ], unit: "deg/s", propertyID: "angularVelocity", @@ -843,25 +1169,25 @@ const GROUPS = [ { label: "Angular Damping", type: "number", - fixedDecimals: 4, + decimals: 4, propertyID: "angularDamping", }, { label: "Bounciness", type: "number", - fixedDecimals: 4, + decimals: 4, propertyID: "restitution", }, { label: "Friction", type: "number", - fixedDecimals: 4, + decimals: 4, propertyID: "friction", }, { label: "Density", type: "number", - fixedDecimals: 4, + decimals: 4, propertyID: "density", }, { @@ -877,6 +1203,7 @@ const GROUPS = [ type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], + decimals: 4, unit: "m/s2", propertyID: "acceleration", }, @@ -894,11 +1221,13 @@ const GROUPS_PER_TYPE = { Web: [ 'base', 'web', 'spatial', 'collision', 'behavior', 'physics' ], Light: [ 'base', 'light', 'spatial', 'collision', 'behavior', 'physics' ], Material: [ 'base', 'material', 'spatial', 'behavior' ], - ParticleEffect: [ 'base', 'spatial', 'behavior', 'physics' ], + ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', + 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], Multiple: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], }; const EDITOR_TIMEOUT_DURATION = 1500; +const DEBOUNCE_TIMEOUT = 125; const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MATERIAL_PREFIX_STRING = "mat::"; @@ -921,10 +1250,31 @@ function debugPrint(message) { } -// GENERAL PROPERTY/GROUP FUNCTIONS +/** + * GENERAL PROPERTY/GROUP FUNCTIONS + */ -function getPropertyElement(propertyID) { - return properties[propertyID].el; +function getPropertyInputElement(propertyID) { + let property = properties[propertyID]; + switch (property.data.type) { + case 'string': + case 'bool': + case 'number': + case 'slider': + case 'dropdown': + case 'textarea': + case 'texture': + return property.elInput; + case 'vec3': + case 'vec2': + return { x: property.elInputX, y: property.elInputY, z: property.elInputZ }; + case 'color': + return { red: property.elInputR, green: property.elInputG, blue: property.elInputB }; + case 'icon': + return property.elLabel; + default: + return undefined; + } } function enableChildren(el, selector) { @@ -944,7 +1294,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); enableChildren(document, ".colpick"); - var elLocked = getPropertyElement("locked"); + var elLocked = getPropertyInputElement("locked"); if (elLocked.checked === false) { removeStaticUserData(); @@ -958,7 +1308,7 @@ function disableProperties() { for (var pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - var elLocked = getPropertyElement("locked"); + var elLocked = getPropertyInputElement("locked"); if (elLocked.checked === true) { if ($('#property-userData-editor').css('display') === "block") { @@ -971,74 +1321,70 @@ function disableProperties() { } function showPropertyElement(propertyID, show) { - let elProperty = properties[propertyID].el; - let elNode = elProperty; - if (elNode.nodeName !== "DIV") { - let elParent = elProperty.parentNode; - if (elParent === undefined && elProperty instanceof Array) { - elParent = elProperty[0].parentNode; - } - if (elParent !== undefined) { - elNode = elParent; - } - } - elNode.style.display = show ? "table" : "none"; + let elProperty = properties[propertyID].elProperty; + elProperty.style.display = show ? "table" : "none"; } function resetProperties() { for (let propertyID in properties) { - let elProperty = properties[propertyID].el; - let propertyData = properties[propertyID].data; + let property = properties[propertyID]; + let propertyData = property.data; switch (propertyData.type) { case 'string': { - elProperty.value = ""; + property.elInput.value = ""; break; } case 'bool': { - elProperty.checked = false; + property.elInput.checked = false; break; } - case 'number': { + case 'number': + case 'slider': { if (propertyData.defaultValue !== undefined) { - elProperty.value = propertyData.defaultValue; + property.elInput.value = propertyData.defaultValue; } else { - elProperty.value = ""; + property.elInput.value = ""; + } + if (property.elSlider !== undefined) { + property.elSlider.value = property.elInput.value; } break; } case 'vec3': case 'vec2': { - // vec2/vec3 are array of 2/3 elInput numbers - elProperty[0].value = ""; - elProperty[1].value = ""; - if (elProperty[2] !== undefined) { - elProperty[2].value = ""; + property.elInputX.value = ""; + property.elInputY.value = ""; + if (property.elInputZ !== undefined) { + property.elInputZ.value = ""; } break; } case 'color': { - // color is array of color picker and 3 elInput numbers - elProperty[0].style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - elProperty[1].value = ""; - elProperty[2].value = ""; - elProperty[3].value = ""; + property.elColorPicker.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + property.elInputR.value = ""; + property.elInputG.value = ""; + property.elInputB.value = ""; break; } case 'dropdown': { - elProperty.value = ""; - setDropdownText(elProperty); + property.elInput.value = ""; + setDropdownText(property.elInput); break; } case 'textarea': { - elProperty.value = ""; - setTextareaScrolling(elProperty); + property.elInput.value = ""; + setTextareaScrolling(property.elInput); break; } case 'icon': { - // icon is array of elSpan (icon glyph) and elLabel - elProperty[0].style.display = "none"; - elProperty[1].innerHTML = propertyData.label; + property.elSpan.style.display = "none"; + property.elLabel.innerHTML = propertyData.label; + break; + } + case 'texture': { + property.elInput.value = ""; + property.elInput.imageLoad(property.elInput.value); break; } } @@ -1069,8 +1415,34 @@ function showGroupsForType(type) { } } +function getPropertyValue(propertyName) { + // if this is a compound property name (i.e. animation.running) + // then split it by . up to 3 times to find property value + let propertyValue; + let splitPropertyName = propertyName.split('.'); + if (splitPropertyName.length > 1) { + let propertyGroupName = splitPropertyName[0]; + let subPropertyName = splitPropertyName[1]; + let groupProperties = selectedEntityProperties[propertyGroupName]; + if (groupProperties === undefined || groupProperties[subPropertyName] === undefined) { + return undefined; + } + if (splitPropertyName.length === 3) { + let subSubPropertyName = splitPropertyName[2]; + propertyValue = groupProperties[subPropertyName][subSubPropertyName]; + } else { + propertyValue = groupProperties[subPropertyName]; + } + } else { + propertyValue = selectedEntityProperties[propertyName]; + } + return propertyValue; +} -// PROPERTY UPDATE FUNCTIONS + +/** + * PROPERTY UPDATE FUNCTIONS + */ function updateProperty(propertyName, propertyValue) { let propertyUpdate = {}; @@ -1114,26 +1486,24 @@ function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { }; } -function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { - decimals = ((decimals === undefined) ? 4 : decimals); +function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, decimals) { return function() { - let value = parseFloat(this.value).toFixed(decimals); + let value = parseFloat(this.value); + if (multiplier !== undefined) { + value *= multiplier; + } + if (decimals !== undefined) { + value = value.toFixed(decimals); + } updateProperty(propertyName, value); }; } -function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY) { - return function () { - let newValue = { - x: elX.value, - y: elY.value - }; - updateProperty(propertyName, newValue); - }; -} - -function createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elX, elY, multiplier) { +function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier) { return function () { + if (multiplier === undefined) { + multiplier = 1; + } let newValue = { x: elX.value * multiplier, y: elY.value * multiplier @@ -1142,19 +1512,11 @@ function createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elX, e }; } -function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ) { - return function() { - let newValue = { - x: elX.value, - y: elY.value, - z: elZ ? elZ.value : 0 - }; - updateProperty(propertyName, newValue); - }; -} - -function createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elX, elY, elZ, multiplier) { +function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier) { return function() { + if (multiplier === undefined) { + multiplier = 1; + } let newValue = { x: elX.value * multiplier, y: elY.value * multiplier, @@ -1199,14 +1561,16 @@ function createImageURLUpdateFunction(propertyName) { } -// PROPERTY ELEMENT CREATION FUNCTIONS +/** + * PROPERTY ELEMENT CREATION FUNCTIONS + */ function createStringProperty(property, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property text"); + elProperty.className = "property text"; let elInput = document.createElement('input'); elInput.setAttribute("id", elementID); @@ -1232,7 +1596,7 @@ function createBoolProperty(property, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property checkbox"); + elProperty.className = "property checkbox"; if (propertyData.glyph !== undefined) { elLabel.innerText = " " + propertyData.label; @@ -1260,15 +1624,108 @@ function createBoolProperty(property, elProperty, elLabel) { return elInput; } -function createVec3Property(property, elProperty, elLabel) { +function createNumberProperty(property, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property " + propertyData.vec3Type + " fstuple"); + elProperty.className = "property number"; + + addUnit(propertyData.unit, elLabel); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "number"); + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + } + + let defaultValue = propertyData.defaultValue; + if (defaultValue !== undefined) { + elInput.value = defaultValue; + } + + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.muliplier, propertyData.decimals)); + + elProperty.appendChild(elLabel); + elProperty.appendChild(elInput); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, true); + } + + return elInput; +} + +function createSliderProperty(property, elProperty, elLabel) { + let propertyData = property.data; + + elProperty.className = "property range"; + + let elDiv = document.createElement("div"); + elDiv.className = "slider-wrapper"; + + let elSlider = document.createElement("input"); + elSlider.setAttribute("type", "range"); + + let elInput = document.createElement("input"); + elInput.setAttribute("type", "number"); + + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + elSlider.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + elSlider.setAttribute("max", propertyData.max); + elSlider.setAttribute("data-max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + elSlider.setAttribute("step", propertyData.step); + } + + elInput.oninput = function (event) { + let value = event.target.value; + elSlider.value = value; + if (propertyData.multiplier !== undefined) { + value *= propertyData.multiplier; + } + updateProperty(property.name, value); + }; + elInput.onchange = elInput.oninput; + elSlider.oninput = function (event) { + let value = event.target.value; + elInput.value = value; + if (propertyData.multiplier !== undefined) { + value *= propertyData.multiplier; + } + updateProperty(property.name, value); + }; + + elDiv.appendChild(elLabel); + elDiv.appendChild(elSlider); + elDiv.appendChild(elInput); + elProperty.appendChild(elDiv); + + return [ elSlider, elInput ]; +} + +function createVec3Property(property, elProperty, elLabel) { + let propertyName = property.name; + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "property " + propertyData.vec3Type + " fstuple"; let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); + elTuple.className = "tuple"; addUnit(propertyData.unit, elLabel); @@ -1282,13 +1739,8 @@ function createVec3Property(property, elProperty, elLabel) { let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[2], propertyData.min, propertyData.max, propertyData.step); - let inputChangeFunction; - if (propertyData.multiplier !== undefined) { - inputChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, - elInputZ, propertyData.multiplier); - } else { - inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ); - } + let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, + elInputZ, propertyData.multiplier); elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); elInputZ.addEventListener('change', inputChangeFunction); @@ -1301,10 +1753,10 @@ function createVec2Property(property, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property " + propertyData.vec2Type + " fstuple"); + elProperty.className = "property " + propertyData.vec2Type + " fstuple"; let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); + elTuple.className = "tuple"; addUnit(propertyData.unit, elLabel); @@ -1316,13 +1768,8 @@ function createVec2Property(property, elProperty, elLabel) { let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], propertyData.min, propertyData.max, propertyData.step); - let inputChangeFunction; - if (propertyData.multiplier !== undefined) { - inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY, - propertyData.multiplier); - } else { - inputChangeFunction = createEmitVec2PropertyUpdateFunctionWithMultiplier(propertyName, elInputX, elInputY); - } + let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, + elInputY, propertyData.multiplier); elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); @@ -1333,14 +1780,14 @@ function createColorProperty(property, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; - elProperty.setAttribute("class", "property rgb fstuple"); + elProperty.className = "property rgb fstuple"; let elColorPicker = document.createElement('div'); - elColorPicker.setAttribute("class", "color-picker"); + elColorPicker.className = "color-picker"; elColorPicker.setAttribute("id", elementID); let elTuple = document.createElement('div'); - elTuple.setAttribute("class", "tuple"); + elTuple.className = "tuple"; elProperty.appendChild(elColorPicker); elProperty.appendChild(elLabel); @@ -1388,7 +1835,7 @@ function createDropdownProperty(property, propertyID, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property dropdown"); + elProperty.className = "property dropdown"; let elInput = document.createElement('select'); elInput.setAttribute("id", elementID); @@ -1409,43 +1856,12 @@ function createDropdownProperty(property, propertyID, elProperty, elLabel) { return elInput; } -function createNumberProperty(property, elProperty, elLabel) { - let propertyName = property.name; - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.setAttribute("class", "property number"); - - addUnit(propertyData.unit, elLabel); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", elementID); - elInput.setAttribute("type", "number"); - - let defaultValue = propertyData.defaultValue; - if (defaultValue !== undefined) { - elInput.value = defaultValue; - } - - let fixedDecimals = propertyData.fixedDecimals !== undefined ? propertyData.fixedDecimals : 0; - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, fixedDecimals)); - - elProperty.appendChild(elLabel); - elProperty.appendChild(elInput); - - if (propertyData.buttons !== undefined) { - addButtons(elProperty, elementID, propertyData.buttons, true); - } - - return elInput; -} - function createTextareaProperty(property, elProperty, elLabel) { let propertyName = property.name; let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property textarea"); + elProperty.className = "property textarea"; elProperty.appendChild(elLabel); @@ -1470,7 +1886,7 @@ function createIconProperty(property, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property value"); + elProperty.className = "property value"; elLabel.setAttribute("id", elementID); elLabel.innerHTML = " " + propertyData.label; @@ -1484,11 +1900,62 @@ function createIconProperty(property, elProperty, elLabel) { return [ elSpan, elLabel ]; } +function createTextureProperty(property, elProperty, elLabel) { + let elementID = property.elementID; + + elProperty.className = "property texture"; + + let elDiv = document.createElement("div"); + let elImage = document.createElement("img"); + elDiv.className = "texture-image no-texture"; + elDiv.appendChild(elImage); + + let elInput = document.createElement('input'); + elInput.setAttribute("id", elementID); + elInput.setAttribute("type", "text"); + + let imageLoad = _.debounce(function (url) { + if (url.slice(0, 5).toLowerCase() === "atp:/") { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-texture"); + elDiv.classList.add("no-preview"); + } else if (url.length > 0) { + elDiv.classList.remove("no-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("with-texture"); + elImage.src = url; + elImage.style.display = "block"; + } else { + elImage.src = ""; + elImage.style.display = "none"; + elDiv.classList.remove("with-texture"); + elDiv.classList.remove("no-preview"); + elDiv.classList.add("no-texture"); + } + }, DEBOUNCE_TIMEOUT * 2); + elInput.imageLoad = imageLoad; + elInput.oninput = function (event) { + // Add throttle + var url = event.target.value; + imageLoad(url); + updateProperty(property.name, url) + }; + elInput.onchange = elInput.oninput; + + elProperty.appendChild(elLabel); + elProperty.appendChild(elDiv); + elProperty.appendChild(elInput); + + return [ elImage, elInput ]; +} + function createButtonsProperty(property, elProperty, elLabel) { let elementID = property.elementID; let propertyData = property.data; - elProperty.setAttribute("class", "property text"); + elProperty.className = "property text"; let hasLabel = propertyData.label !== undefined; if (hasLabel) { @@ -1511,9 +1978,12 @@ function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, elLabel.setAttribute("for", elementID); let elInput = document.createElement('input'); + elInput.className = subLabel; elInput.setAttribute("id", elementID); elInput.setAttribute("type", "number"); - elInput.setAttribute("class", subLabel); + elInput.setAttribute("min", min); + elInput.setAttribute("max", max); + elInput.setAttribute("step", step); elDiv.appendChild(elInput); elDiv.appendChild(elLabel); @@ -1525,7 +1995,7 @@ function createTupleNumberInput(elTuple, propertyElementID, subLabel, min, max, function addUnit(unit, elLabel) { if (unit !== undefined) { let elSpan = document.createElement('span'); - elSpan.setAttribute("class", "unit"); + elSpan.className = "unit"; elSpan.innerHTML = unit; elLabel.appendChild(elSpan); } @@ -1533,12 +2003,12 @@ function addUnit(unit, elLabel) { function addButtons(elProperty, propertyID, buttons, newRow) { let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "row"); + elDiv.className = "row"; buttons.forEach(function(button) { let elButton = document.createElement('input'); + elButton.className = button.className; elButton.setAttribute("type", "button"); - elButton.setAttribute("class", button.className); elButton.setAttribute("id", propertyID + "-button-" + button.id); elButton.setAttribute("value", button.label); elButton.addEventListener("click", button.onClick); @@ -1556,7 +2026,9 @@ function addButtons(elProperty, propertyID, buttons, newRow) { } -// BUTTON CALLBACKS +/** + * BUTTON CALLBACKS + */ function rescaleDimensions() { EventBridge.emitWebEvent(JSON.stringify({ @@ -1604,16 +2076,18 @@ function reloadServerScripts() { } function copySkyboxURLToAmbientURL() { - let skyboxURL = getPropertyElement("skybox.url").value; - getPropertyElement("ambientLight.ambientURL").value = skyboxURL; + let skyboxURL = getPropertyInputElement("skybox.url").value; + getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL; updateProperty("ambientLight.ambientURL", skyboxURL); } -// USER DATA FUNCTIONS +/** + * USER DATA FUNCTIONS + */ function clearUserData() { - let elUserData = getPropertyElement("userData"); + let elUserData = getPropertyInputElement("userData"); deleteJSONEditor(); elUserData.value = ""; showUserDataTextArea(); @@ -1831,10 +2305,12 @@ function saveJSONUserData(noUpdate) { } -// MATERIAL DATA FUNCTIONS +/** + * MATERIAL DATA FUNCTIONS + */ function clearMaterialData() { - let elMaterialData = getPropertyElement("materialData"); + let elMaterialData = getPropertyInputElement("materialData"); deleteJSONMaterialEditor(); elMaterialData.value = ""; showMaterialDataTextArea(); @@ -2015,7 +2491,9 @@ function bindAllNonJSONEditorElements() { } -// DROPDOWN FUNCTIONS +/** + * DROPDOWN FUNCTIONS + */ function setDropdownText(dropdown) { let lis = dropdown.parentNode.getElementsByTagName("li"); @@ -2051,7 +2529,9 @@ function setDropdownValue(event) { } -// TEXTAREA / PARENT MATERIAL NAME FUNCTIONS +/** + * TEXTAREA / PARENT MATERIAL NAME FUNCTIONS + */ function setTextareaScrolling(element) { var isScrolling = element.scrollHeight > element.offsetHeight; @@ -2084,17 +2564,17 @@ function loaded() { fieldset.appendChild(elGroup); } else { elGroup = document.createElement('fieldset'); - elGroup.setAttribute("class", "major"); + elGroup.className = "major"; elGroup.setAttribute("id", "properties-" + group.id); elPropertiesList.appendChild(elGroup); } if (group.label !== undefined) { let elLegend = document.createElement('legend'); + elLegend.className = "section-header"; elLegend.innerText = group.label; - elLegend.setAttribute("class", "section-header"); let elSpan = document.createElement('span'); - elSpan.setAttribute("class", ".collapse-icon"); + elSpan.className = ".collapse-icon"; elSpan.innerText = "M"; elLegend.appendChild(elSpan); elGroup.appendChild(elLegend); @@ -2109,8 +2589,8 @@ function loaded() { let elProperty; if (propertyType === "sub-header") { elProperty = document.createElement('legend'); + elProperty.className = "sub-section-header"; elProperty.innerText = propertyData.label; - elProperty.setAttribute("class", "sub-section-header"); } else { elProperty = document.createElement('div'); elProperty.setAttribute("id", "div-" + propertyElementID); @@ -2124,12 +2604,12 @@ function loaded() { let elColumnDiv = document.getElementById(columnDivName); if (!elColumnDiv) { elColumnDiv = document.createElement('div'); - elColumnDiv.setAttribute("class", "two-column"); + elColumnDiv.className = "two-column"; elColumnDiv.setAttribute("id", group.id + "columnDiv"); elGroup.appendChild(elColumnDiv); } elColumn = document.createElement('fieldset'); - elColumn.setAttribute("class", "column"); + elColumn.className = "column"; elColumn.setAttribute("id", columnName); elColumnDiv.appendChild(elColumn); } @@ -2142,53 +2622,79 @@ function loaded() { elLabel.innerText = propertyData.label; elLabel.setAttribute("for", propertyElementID); - properties[propertyID] = { data: propertyData, elementID: propertyElementID, name: propertyName }; - - let property = properties[propertyID]; + let property = { + data: propertyData, + elementID: propertyElementID, + name: propertyName, + elProperty: elProperty + }; + properties[propertyID] = property; switch (propertyType) { case 'string': { - properties[propertyID].el = createStringProperty(property, elProperty, elLabel); + properties[propertyID].elInput = createStringProperty(property, elProperty, elLabel); break; } case 'bool': { - properties[propertyID].el = createBoolProperty(property, elProperty, elLabel); + properties[propertyID].elInput = createBoolProperty(property, elProperty, elLabel); break; } case 'number': { - properties[propertyID].el = createNumberProperty(property, elProperty, elLabel); + properties[propertyID].elInput = createNumberProperty(property, elProperty, elLabel); + break; + } + case 'slider': { + let elSlider = createSliderProperty(property, elProperty, elLabel); + properties[propertyID].elSlider = elSlider[0]; + properties[propertyID].elInput = elSlider[1]; break; } case 'vec3': { - properties[propertyID].el = createVec3Property(property, elProperty, elLabel); + let elVec3 = createVec3Property(property, elProperty, elLabel); + properties[propertyID].elInputX = elVec3[0]; + properties[propertyID].elInputY = elVec3[1]; + properties[propertyID].elInputZ = elVec3[2]; break; } case 'vec2': { - properties[propertyID].el = createVec2Property(property, elProperty, elLabel); + let elVec2 = createVec2Property(property, elProperty, elLabel); + properties[propertyID].elInputX = elVec2[0]; + properties[propertyID].elInputY = elVec2[1]; break; } case 'color': { - properties[propertyID].el = createColorProperty(property, elProperty, elLabel); + let elColor = createColorProperty(property, elProperty, elLabel); + properties[propertyID].elColorPicker = elColor[0]; + properties[propertyID].elInputR = elColor[1]; + properties[propertyID].elInputG = elColor[2]; + properties[propertyID].elInputB = elColor[3]; break; } case 'dropdown': { - properties[propertyID].el = createDropdownProperty(property, propertyID, elProperty, elLabel); + properties[propertyID].elInput = createDropdownProperty(property, propertyID, elProperty, elLabel); break; } case 'textarea': { - properties[propertyID].el = createTextareaProperty(property, elProperty, elLabel); + properties[propertyID].elInput = createTextareaProperty(property, elProperty, elLabel); break; } case 'icon': { - properties[propertyID].el = createIconProperty(property, elProperty, elLabel); + let elIcon = createIconProperty(property, elProperty, elLabel); + properties[propertyID].elSpan = elIcon[0]; + properties[propertyID].elLabel = elIcon[1]; + break; + } + case 'texture': { + let elTexture = createTextureProperty(property, elProperty, elLabel); + properties[propertyID].elImage = elTexture[0]; + properties[propertyID].elInput = elTexture[1]; break; } case 'buttons': { - properties[propertyID].el = createButtonsProperty(property, elProperty, elLabel); + properties[propertyID].elProperty = createButtonsProperty(property, elProperty, elLabel); break; } case 'sub-header': { - properties[propertyID].el = elProperty; break; } default: { @@ -2258,13 +2764,13 @@ function loaded() { showGroupsForType("None"); deleteJSONEditor(); - getPropertyElement("userData").value = ""; + getPropertyInputElement("userData").value = ""; showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); deleteJSONMaterialEditor(); - getPropertyElement("materialData").value = ""; + getPropertyInputElement("materialData").value = ""; showMaterialDataTextArea(); showSaveMaterialDataButton(); showNewJSONMaterialEditorButton(); @@ -2299,16 +2805,13 @@ function loaded() { showGroupsForType(type); let typeProperty = properties["type"]; - let elTypeProperty = typeProperty.el; - elTypeProperty[0].innerHTML = typeProperty.data.icons[type]; - elTypeProperty[0].style.display = "inline-block"; - elTypeProperty[1].innerHTML = type + " (" + data.selections.length + ")"; + typeProperty.elSpan.innerHTML = typeProperty.data.icons[type]; + typeProperty.elSpan.style.display = "inline-block"; + typeProperty.elLabel.innerHTML = type + " (" + data.selections.length + ")"; disableProperties(); } else { - selectedEntityProperties = data.selections[0].properties; - - showGroupsForType(selectedEntityProperties.type); + selectedEntityProperties = data.selections[0].properties; if (lastEntityID !== '"' + selectedEntityProperties.id + '"' && lastEntityID !== null) { if (editor !== null) { @@ -2335,41 +2838,26 @@ function loaded() { } } + showGroupsForType(selectedEntityProperties.type); + for (let propertyID in properties) { let property = properties[propertyID]; - let elProperty = property.el; let propertyData = property.data; let propertyName = property.name; - - // if this is a compound property name (i.e. animation.running) - // then split it by . up to 3 times to find property value - let propertyValue; - let splitPropertyName = propertyName.split('.'); - if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; - let groupProperties = selectedEntityProperties[propertyGroupName]; - if (groupProperties === undefined || groupProperties[subPropertyName] === undefined) { - continue; - } - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - propertyValue = groupProperties[subPropertyName][subSubPropertyName]; - } else { - propertyValue = groupProperties[subPropertyName]; - } - } else { - propertyValue = selectedEntityProperties[propertyName]; - } + let propertyValue = getPropertyValue(propertyName); let isSubProperty = propertyData.subPropertyOf !== undefined; - if (elProperty === undefined || (propertyValue === undefined && !isSubProperty)) { + if (propertyValue === undefined && !isSubProperty) { continue; } + if (!propertyValue && propertyData.fallbackProperty !== undefined) { + propertyValue = getPropertyValue(propertyData.fallbackProperty); + } + switch (propertyData.type) { case 'string': { - elProperty.value = propertyValue; + property.elInput.value = propertyValue; break; } case 'bool': { @@ -2378,53 +2866,64 @@ function loaded() { let propertyValue = selectedEntityProperties[propertyData.subPropertyOf]; let subProperties = propertyValue.split(","); let subPropertyValue = subProperties.indexOf(propertyID) > -1; - elProperty.checked = inverse ? !subPropertyValue : subPropertyValue; + property.elInput.checked = inverse ? !subPropertyValue : subPropertyValue; } else { - elProperty.checked = inverse ? !propertyValue : propertyValue; + property.elInput.checked = inverse ? !propertyValue : propertyValue; } break; } - case 'number': { - let fixedDecimals = propertyData.fixedDecimals !== undefined ? propertyData.fixedDecimals : 0; - elProperty.value = propertyValue.toFixed(fixedDecimals); + case 'number': + case 'slider': { + let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; + let decimals = propertyData.decimals !== undefined ? propertyData.decimals : 0; + property.elInput.value = (propertyValue / multiplier).toFixed(decimals); + if (property.elSlider !== undefined) { + property.elSlider.value = property.elInput.value; + } break; } case 'vec3': case 'vec2': { - // vec2/vec3 are array of 2/3 elInput numbers let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; - elProperty[0].value = (propertyValue.x / multiplier).toFixed(4); - elProperty[1].value = (propertyValue.y / multiplier).toFixed(4); - if (elProperty[2] !== undefined) { - elProperty[2].value = (propertyValue.z / multiplier).toFixed(4); + let decimals = propertyData.decimals !== undefined ? propertyData.decimals : 0; + property.elInputX.value = (propertyValue.x / multiplier).toFixed(decimals); + property.elInputY.value = (propertyValue.y / multiplier).toFixed(decimals); + if (property.elInputZ !== undefined) { + property.elInputZ.value = (propertyValue.z / multiplier).toFixed(decimals); } break; } case 'color': { - // color is array of color picker and 3 elInput numbers - elProperty[0].style.backgroundColor = "rgb(" + propertyValue.red + "," + - propertyValue.green + "," + - propertyValue.blue + ")"; - elProperty[1].value = propertyValue.red; - elProperty[2].value = propertyValue.green; - elProperty[3].value = propertyValue.blue; + if (!propertyValue.red && propertyData.fallbackProperty !== undefined) { + propertyValue = getPropertyValue(propertyData.fallbackProperty); + } + property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," + + propertyValue.green + "," + + propertyValue.blue + ")"; + property.elInputR.value = propertyValue.red; + property.elInputG.value = propertyValue.green; + property.elInputB.value = propertyValue.blue; break; } case 'dropdown': { - elProperty.value = propertyValue; - setDropdownText(elProperty); + property.elInput.value = propertyValue; + setDropdownText(property.elInput); break; } case 'textarea': { - elProperty.value = propertyValue; - setTextareaScrolling(elProperty); + property.elInput.value = propertyValue; + setTextareaScrolling(property.elInput); break; } case 'icon': { - // icon is array of elSpan (icon glyph) and elLabel - elProperty[0].innerHTML = propertyData.icons[propertyValue]; - elProperty[0].style.display = "inline-block"; - elProperty[1].innerHTML = propertyValue; + property.elSpan.innerHTML = propertyData.icons[propertyValue]; + property.elSpan.style.display = "inline-block"; + property.elLabel.innerHTML = propertyValue; + break; + } + case 'texture': { + property.elInput.value = propertyValue; + property.elInput.imageLoad(property.elInput.value); break; } } @@ -2439,10 +2938,10 @@ function loaded() { } } - let elGrabbable = getPropertyElement("grabbable"); - let elTriggerable = getPropertyElement("triggerable"); - let elIgnoreIK = getPropertyElement("ignoreIK"); - elGrabbable.checked = getPropertyElement("dynamic").checked; + let elGrabbable = getPropertyInputElement("grabbable"); + let elTriggerable = getPropertyInputElement("triggerable"); + let elIgnoreIK = getPropertyInputElement("ignoreIK"); + elGrabbable.checked = getPropertyInputElement("dynamic").checked; elTriggerable.checked = false; elIgnoreIK.checked = true; let grabbablesSet = false; @@ -2481,11 +2980,11 @@ function loaded() { if (selectedEntityProperties.type === "Image") { let imageLink = JSON.parse(selectedEntityProperties.textures)["tex.picture"]; - getPropertyElement("image").value = imageLink; + getPropertyInputElement("image").value = imageLink; } else if (selectedEntityProperties.type === "Material") { - let elParentMaterialNameString = getPropertyElement("materialNameToReplace"); - let elParentMaterialNameNumber = getPropertyElement("submeshToReplace"); - let elParentMaterialNameCheckbox = getPropertyElement("selectSubmesh"); + let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); let parentMaterialName = selectedEntityProperties.parentMaterialName; if (parentMaterialName.startsWith(MATERIAL_PREFIX_STRING)) { elParentMaterialNameString.value = parentMaterialName.replace(MATERIAL_PREFIX_STRING, ""); @@ -2504,7 +3003,7 @@ function loaded() { } catch (e) { // normal text deleteJSONEditor(); - getPropertyElement("userData").value = selectedEntityProperties.userData; + getPropertyInputElement("userData").value = selectedEntityProperties.userData; showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); @@ -2527,7 +3026,7 @@ function loaded() { } catch (e) { // normal text deleteJSONMaterialEditor(); - getPropertyElement("materialData").value = selectedEntityProperties.materialData; + getPropertyInputElement("materialData").value = selectedEntityProperties.materialData; showMaterialDataTextArea(); showNewJSONMaterialEditorButton(); hideSaveMaterialDataButton(); @@ -2546,7 +3045,7 @@ function loaded() { if (selectedEntityProperties.locked) { disableProperties(); - getPropertyElement("locked").removeAttribute('disabled'); + getPropertyInputElement("locked").removeAttribute('disabled'); } else { enableProperties(); disableSaveUserDataButton(); @@ -2564,10 +3063,10 @@ function loaded() { // Server Script Status let serverScriptProperty = properties["serverScripts"]; - let elServerScript = serverScriptProperty.el; + let elServerScript = serverScriptProperty.elInput; let serverScriptElementID = serverScriptProperty.elementID; let elDiv = document.createElement('div'); - elDiv.setAttribute("class", "property"); + elDiv.className = "property"; let elLabel = document.createElement('label'); elLabel.setAttribute("for", serverScriptElementID + "-status"); elLabel.innerText = "Server Script Status"; @@ -2579,19 +3078,19 @@ function loaded() { // Server Script Error elDiv = document.createElement('div'); - elDiv.setAttribute("class", "property"); + elDiv.className = "property"; let elServerScriptError = document.createElement('textarea'); elServerScriptError.setAttribute("id", serverScriptElementID + "-error"); elDiv.appendChild(elServerScriptError); elServerScript.parentNode.appendChild(elDiv); - let elScript = getPropertyElement("script"); - elScript.parentNode.setAttribute("class", "property url refresh"); - elServerScript.parentNode.setAttribute("class", "property url refresh"); + let elScript = getPropertyInputElement("script"); + elScript.parentNode.className = "property url refresh"; + elServerScript.parentNode.className = "property url refresh"; // User Data let userDataProperty = properties["userData"]; - let elUserData = userDataProperty.el; + let elUserData = userDataProperty.elInput; let userDataElementID = userDataProperty.elementID; elDiv = elUserData.parentNode; let elStaticUserData = document.createElement('div'); @@ -2607,7 +3106,7 @@ function loaded() { // Material Data let materialDataProperty = properties["materialData"]; - let elMaterialData = materialDataProperty.el; + let elMaterialData = materialDataProperty.elInput; let materialDataElementID = materialDataProperty.elementID; elDiv = elMaterialData.parentNode; let elStaticMaterialData = document.createElement('div'); @@ -2622,9 +3121,9 @@ function loaded() { elDiv.insertBefore(elMaterialDataEditor, elMaterialData); // User Data Fields - let elGrabbable = getPropertyElement("grabbable"); - let elTriggerable = getPropertyElement("triggerable"); - let elIgnoreIK = getPropertyElement("ignoreIK"); + let elGrabbable = getPropertyInputElement("grabbable"); + let elTriggerable = getPropertyInputElement("triggerable"); + let elIgnoreIK = getPropertyInputElement("ignoreIK"); elGrabbable.addEventListener('change', function() { userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); }); @@ -2636,9 +3135,9 @@ function loaded() { }); // Special Property Callbacks - let elParentMaterialNameString = getPropertyElement("materialNameToReplace"); - let elParentMaterialNameNumber = getPropertyElement("submeshToReplace"); - let elParentMaterialNameCheckbox = getPropertyElement("selectSubmesh"); + let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); + let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); + let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); elParentMaterialNameString.addEventListener('change', function () { updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); }); @@ -2655,7 +3154,7 @@ function loaded() { } }); - getPropertyElement("image").addEventListener('change', createImageURLUpdateFunction('textures')); + getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures')); // Collapsible sections let elCollapsible = document.getElementsByClassName("section-header"); @@ -2704,7 +3203,8 @@ function loaded() { // let elDropdowns = document.getElementsByTagName("select"); for (let dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) { - let options = elDropdowns[dropDownIndex].getElementsByTagName("option"); + let elDropdown = elDropdowns[dropDownIndex]; + let options = elDropdown.getElementsByTagName("option"); let selectedOption = 0; for (let optionIndex = 0; optionIndex < options.length; ++optionIndex) { if (options[optionIndex].getAttribute("selected") === "selected") { @@ -2712,14 +3212,14 @@ function loaded() { // TODO: Shouldn't there be a break here? } } - let div = elDropdowns[dropDownIndex].parentNode; + let div = elDropdown.parentNode; let dl = document.createElement("dl"); div.appendChild(dl); let dt = document.createElement("dt"); - dt.name = elDropdowns[dropDownIndex].name; - dt.id = elDropdowns[dropDownIndex].id; + dt.name = elDropdown.name; + dt.id = elDropdown.id; dt.addEventListener("click", toggleDropdown, true); dl.appendChild(dt); @@ -2746,9 +3246,9 @@ function loaded() { ul.appendChild(li); } - let propertyID = elDropdowns[dropDownIndex].getAttribute("propertyID"); + let propertyID = elDropdown.getAttribute("propertyID"); let property = properties[propertyID]; - property.el = dt; + property.elInput = dt; dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name)); } diff --git a/scripts/system/html/js/underscore-min.js b/scripts/system/html/js/underscore-min.js new file mode 100644 index 0000000000..f01025b7bc --- /dev/null +++ b/scripts/system/html/js/underscore-min.js @@ -0,0 +1,6 @@ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. +(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); +//# sourceMappingURL=underscore-min.map \ No newline at end of file From 078baa86e4fbe56cd095e2ea46c59abe0dc34e05 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 16 Oct 2018 13:16:01 -0700 Subject: [PATCH 116/276] Sitting abort post transit animation --- interface/src/avatar/AvatarManager.cpp | 2 +- libraries/animation/src/Rig.h | 1 + .../src/avatars-renderer/Avatar.cpp | 13 +++++++++++-- .../avatars-renderer/src/avatars-renderer/Avatar.h | 9 +++++++++ .../controllers/controllerModules/teleport.js | 2 ++ 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 373ae9980a..c3f6579e90 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -162,7 +162,7 @@ void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { case AvatarTransit::Status::END_TRANSIT: qDebug() << "END_TRANSIT"; _myAvatar->overrideNetworkAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, - endAnimation._firstFrame + endAnimation._frameCount); + endAnimation._firstFrame + endAnimation._frameCount); break; case AvatarTransit::Status::ENDED: qDebug() << "END_FRAME"; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e1012df029..37d1ec1dd3 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -116,6 +116,7 @@ public: void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreAnimation(); void restoreNetworkAnimation(); + QStringList getAnimationRoles() const; void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreRoleAnimation(const QString& role); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index b2747277c9..b43e1c23f6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -132,9 +132,12 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av const float SETTLE_ABORT_DISTANCE = 0.1f; float scaledSettleAbortDistance = SETTLE_ABORT_DISTANCE * _scale; - if (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT) { + bool abortPostTransit = (_status == Status::POST_TRANSIT && _purpose == Purpose::SIT) || + (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT); + if (abortPostTransit) { reset(); _status = Status::ENDED; + _purpose = Purpose::UNDEFINED; } return _status; } @@ -203,6 +206,7 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { _currentPosition = _endPosition; if (nextTime >= _totalTime) { _isActive = false; + _purpose = Purpose::UNDEFINED; status = Status::ENDED; } else if (_currentTime < _totalTime - _postTransitTime) { status = Status::END_TRANSIT; @@ -2000,7 +2004,12 @@ AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& av void Avatar::setTransitScale(float scale) { std::lock_guard lock(_transitLock); - return _transit.setScale(scale); + _transit.setScale(scale); +} + +void Avatar::setTransitPurpose(int purpose) { + std::lock_guard lock(_transitLock); + _transit.setPurpose(static_cast(purpose)); } void Avatar::overrideNextPacketPositionData(const glm::vec3& position) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index e4fd667c1f..d375909609 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -71,6 +71,12 @@ public: EASE_IN_OUT }; + enum Purpose { + UNDEFINED = 0, + TELEPORT, + SIT + }; + struct TransitAnimation { TransitAnimation(){}; TransitAnimation(const QString& animationUrl, int firstFrame, int frameCount) : @@ -99,6 +105,7 @@ public: glm::vec3 getCurrentPosition() { return _currentPosition; } glm::vec3 getEndPosition() { return _endPosition; } void setScale(float scale) { _scale = scale; } + void setPurpose(const Purpose& purpose) { _purpose = purpose; } void reset(); private: @@ -123,6 +130,7 @@ private: EaseType _easeType { EaseType::EASE_OUT }; Status _status { Status::IDLE }; float _scale { 1.0f }; + Purpose _purpose { Purpose::UNDEFINED }; }; class Avatar : public AvatarData, public scriptable::ModelProvider { @@ -449,6 +457,7 @@ public: AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); void setTransitScale(float scale); + Q_INVOKABLE void setTransitPurpose(int purpose); void overrideNextPacketPositionData(const glm::vec3& position); diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index bf5022cdaf..d31f207b19 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -781,11 +781,13 @@ Script.include("/~/system/libraries/controllers.js"); if (target === TARGET.NONE || target === TARGET.INVALID) { // Do nothing } else if (target === TARGET.SEAT) { + MyAvatar.setTransitPurpose(2); Entities.callEntityMethod(result.objectID, 'sit'); } else if (target === TARGET.SURFACE || target === TARGET.DISCREPANCY) { var offset = getAvatarFootOffset(); result.intersection.y += offset; var shouldLandSafe = target === TARGET.DISCREPANCY; + MyAvatar.setTransitPurpose(1); MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false, shouldLandSafe); HMD.centerUI(); MyAvatar.centerBody(); From 0b77eb5e9ce7e5ebbab42f0db90dece9764a0e82 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 16 Oct 2018 14:25:38 -0700 Subject: [PATCH 117/276] Control UUID inclusion in avatar data with new param Increase estimate of avatars to be sent; remove start/end segment in avatar identity writes. --- .../src/avatars/AvatarMixerSlave.cpp | 5 ++-- libraries/avatars/src/AvatarData.cpp | 25 +++++++++++-------- libraries/avatars/src/AvatarData.h | 1 + 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 0e0802bf38..75d36783a5 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -72,9 +72,7 @@ int AvatarMixerSlave::sendIdentityPacket(NLPacketList& packetList, const AvatarM if (destinationNode.getType() == NodeType::Agent && !destinationNode.isUpstream()) { QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious - packetList.startSegment(); packetList.write(individualData); - packetList.endSegment(); _stats.numIdentityPackets++; return individualData.size(); } else { @@ -248,7 +246,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) distribution.reset(); // Estimate number to sort on number sent last frame (with min. of 20). - const int numToSendEst = std::max(nodeData->getNumAvatarsSentLastFrame() * 2, 20); + const int numToSendEst = std::max(int(nodeData->getNumAvatarsSentLastFrame() * 2.5f), 20); // reset the number of sent avatars nodeData->resetNumAvatarsSentLastFrame(); @@ -455,6 +453,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const bool distanceAdjust = true; const bool dropFaceTracking = false; AvatarDataPacket::SendStatus sendStatus; + sendStatus.sendUUID = true; do { auto startSerialize = chrono::high_resolution_clock::now(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 422db2f2ed..3ac8b32d48 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -229,8 +229,7 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro AvatarDataPacket::SendStatus sendStatus; auto avatarByteArray = AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), sendStatus, dropFaceTracking, false, glm::vec3(0), nullptr, 0, &_outboundDataRate); - // Strip UUID - return avatarByteArray.right(avatarByteArray.size() - NUM_BYTES_RFC4122_UUID); + return avatarByteArray; } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, @@ -255,7 +254,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (dataDetail == NoData) { sendStatus.itemFlags = wantedFlags; - QByteArray avatarDataByteArray(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID + sizeof wantedFlags); + QByteArray avatarDataByteArray; + if (sendStatus.sendUUID) { + avatarDataByteArray.append(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID); + } + avatarDataByteArray.append((char*) &wantedFlags, sizeof wantedFlags); return avatarDataByteArray; } @@ -392,13 +395,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent const unsigned char* const startPosition = destinationBuffer; const unsigned char* const packetEnd = destinationBuffer + maxDataSize; - // Packets always have UUID. - memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); - destinationBuffer += NUM_BYTES_RFC4122_UUID; - - unsigned char * packetFlagsLocation = destinationBuffer; - destinationBuffer += sizeof(wantedFlags); - #define AVATAR_MEMCPY(src) \ memcpy(destinationBuffer, &(src), sizeof(src)); \ destinationBuffer += sizeof(src); @@ -409,6 +405,14 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent && (packetEnd - destinationBuffer) >= (ptrdiff_t)(space) \ && (includedFlags |= AvatarDataPacket::flag)) + if (sendStatus.sendUUID) { + memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID); + destinationBuffer += NUM_BYTES_RFC4122_UUID; + } + + unsigned char * packetFlagsLocation = destinationBuffer; + destinationBuffer += sizeof(wantedFlags); + IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { auto startSection = destinationBuffer; if (_overrideGlobalPosition) { @@ -417,7 +421,6 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent AVATAR_MEMCPY(_globalPosition); } - int numBytes = destinationBuffer - startSection; if (outboundDataRateOut) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index aa3d3d328c..1f5b7093b9 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -302,6 +302,7 @@ namespace AvatarDataPacket { struct SendStatus { HasFlags itemFlags { 0 }; + bool sendUUID { false }; int rotationsSent { 0 }; // ie: index of next unsent joint int translationsSent { 0 }; operator bool() { return itemFlags == 0; } From a2aa63e9ed8ee169cef523c22de24f40abd52a9f Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 16 Oct 2018 14:28:52 -0700 Subject: [PATCH 118/276] adjust no entities message --- scripts/system/html/entityList.html | 2 +- scripts/system/html/js/entityList.js | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 06c2be8e73..f9b6fbd59b 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -108,7 +108,7 @@
- No entities found in view within a 100 meter radius. Try moving to a different location and refreshing. + There are no entities to display. Please check your filters or create an entity to begin.
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 0f3f27a547..2b720f614d 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -142,8 +142,6 @@ function loaded() { elInfoToggleGlyph = elInfoToggle.firstChild; elFooter = document.getElementById("footer-text"); elNoEntitiesMessage = document.getElementById("no-entities"); - elNoEntitiesInView = document.getElementById("no-entities-in-view"); - elNoEntitiesRadius = document.getElementById("no-entities-radius"); document.body.onclick = onBodyClick; document.getElementById("entity-name").onclick = function() { @@ -230,8 +228,6 @@ function loaded() { elFilterTypeCheckboxes.appendChild(elDiv); } - elNoEntitiesInView.style.display = "none"; - entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT); @@ -639,10 +635,8 @@ function loaded() { isFilterInView = !isFilterInView; if (isFilterInView) { elFilterInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE); - elNoEntitiesInView.style.display = "inline"; } else { elFilterInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE); - elNoEntitiesInView.style.display = "none"; } EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView })); refreshEntities(); @@ -650,8 +644,6 @@ function loaded() { function onRadiusChange() { elFilterRadius.value = Math.max(elFilterRadius.value, 0); - elNoEntitiesRadius.firstChild.nodeValue = elFilterRadius.value; - elNoEntitiesMessage.style.display = "none"; EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); refreshEntities(); } @@ -765,7 +757,6 @@ function loaded() { refreshEntities(); }); - augmentSpinButtons(); // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked From 952b1122713192f70bb5b1df4fb8ae987da16522 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 16 Oct 2018 15:10:26 -0700 Subject: [PATCH 119/276] changed the transition times to make them shorter sit == 3sec stand == 1sec, also added failsafe for when the average height is above 5ft. this can recover from a missed transition to standing. --- interface/src/avatar/MyAvatar.cpp | 153 ++++++++++++++---------------- interface/src/avatar/MyAvatar.h | 10 +- 2 files changed, 76 insertions(+), 87 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 00a8e0f30e..b59ff675e1 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -464,17 +464,72 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { } } +void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { + const float STANDING_HEIGHT_MULTIPLE = 1.2f; + const float SITTING_HEIGHT_MULTIPLE = 0.833f; + const int SITTING_COUNT_THRESHOLD = 180; + const int STANDING_COUNT_THRESHOLD = 60; + const int SQUATTY_COUNT_THRESHOLD = 600; + + // qCDebug(interfaceapp) << "locked " << getIsSitStandStateLocked() << " away " << getIsAway() << " hmd " << qApp->isHMDMode() << " user height " << _userHeight.get(); + if (!getIsSitStandStateLocked() && !getIsAway() && qApp->isHMDMode()) { + if (getIsInSittingState()) { + if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we recenter upwards then no longer in sitting state + _sitStandStateCount++; + if (_sitStandStateCount > STANDING_COUNT_THRESHOLD) { + _sumUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(false); + } + } else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we are mis labelled as sitting but we are standing in the real world this will + // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sumUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + // here we stay in sit state but reset the average height + setIsInSittingState(true); + } + } else { + // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) + if (_sumUserHeightSensorSpace > 1.52f) { + setIsInSittingState(true); + } else { + // tipping point is average height when sitting. + _tippingPoint = _sumUserHeightSensorSpace; + _sitStandStateCount = 0; + } + } + } else { + // in the standing state + if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + _sitStandStateCount++; + if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sumUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(true); + } + } else { + // use the mode height for the tipping point when we are standing. + _tippingPoint = getCurrentStandingHeight(); + _sitStandStateCount = 0; + } + } + } else { + // if you are away then reset the average and set state to standing. + _sumUserHeightSensorSpace = _userHeight.get(); + _tippingPoint = _userHeight.get(); + setIsInSittingState(false); + } +} + void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders - const float STANDING_HEIGHT_MULTIPLE = 1.2f; - const float SITTING_HEIGHT_MULTIPLE = 0.833f; const float COSINE_THIRTY_DEGREES = 0.866f; - const float COSINE_TEN_DEGREES = 0.9848f; - const int SITTING_COUNT_THRESHOLD = 100; - const int STANDING_COUNT_THRESHOLD = 10; - const int SQUATTY_COUNT_THRESHOLD = 600; float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); @@ -504,13 +559,11 @@ void MyAvatar::update(float deltaTime) { controller::Pose newHeightReading = getControllerPoseInSensorFrame(controller::Action::HEAD); if (newHeightReading.isValid()) { int newHeightReadingInCentimeters = glm::floor(newHeightReading.getTranslation().y * CENTIMETERS_PER_METER); - _sumUserHeightSensorSpace += newHeightReading.getTranslation().y; - _averageUserHeightCount++; + _sumUserHeightSensorSpace = lerp(_sumUserHeightSensorSpace, newHeightReading.getTranslation().y, 0.01f); _recentModeReadings.insert(newHeightReadingInCentimeters); setCurrentStandingHeight(computeStandingHeightMode(newHeightReading)); setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); } - float averageSensorSpaceHeight = _sumUserHeightSensorSpace / _averageUserHeightCount; // if the spine is straight and the head is below the default position by 5 cm then increment squatty count. const float SQUAT_THRESHOLD = 0.05f; @@ -533,76 +586,8 @@ void MyAvatar::update(float deltaTime) { } float angleHeadUp = glm::dot(headUp, glm::vec3(0.0f, 1.0f, 0.0f)); - const int VELOCITY_COUNT_THRESHOLD = 60; // put update sit stand state counts here - if (!getIsSitStandStateLocked()) { - if (!getIsAway()) { - if (qApp->isHMDMode()) { - if (getIsInSittingState()) { - if (newHeightReading.getTranslation().y > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we recenter upwards then no longer in sitting state - _sitStandStateCount++; - if (_sitStandStateCount > STANDING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; - } - _averageUserHeightCount = 1; - setIsInSittingState(false); - } - } else if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleHeadUp > COSINE_THIRTY_DEGREES)) { - // if we are mis labelled as sitting but we are standing in the real world this will - // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; - } - _averageUserHeightCount = 1; - // here we stay in sit state but reset the average height - setIsInSittingState(true); - } - } else { - _sitStandStateCount = 0; - // tipping point is average height when sitting. - _tippingPoint = averageSensorSpaceHeight; - } - } else { - // in the standing state - if ((newHeightReading.getTranslation().y < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) && (angleHeadUp > COSINE_THIRTY_DEGREES)) { - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sitStandStateCount = 0; - _squatCount = 0; - if (newHeightReading.isValid()) { - _sumUserHeightSensorSpace = newHeightReading.getTranslation().y; - _tippingPoint = newHeightReading.getTranslation().y; - } - _averageUserHeightCount = 1; - setIsInSittingState(true); - } - } else { - // use the mode height for the tipping point when we are standing. - _tippingPoint = getCurrentStandingHeight(); - _sitStandStateCount = 0; - } - } - } - } else { - // if you are away then reset the average and set state to standing. - _squatCount = 0; - _sitStandStateCount = 0; - _averageUserHeightCount = 1; - _sumUserHeightSensorSpace = DEFAULT_AVATAR_HEIGHT; - _tippingPoint = DEFAULT_AVATAR_HEIGHT; - setIsInSittingState(false); - } - } + updateSitStandState(newHeightReading.getTranslation().y, angleHeadUp); if (_drawAverageFacingEnabled) { auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); @@ -3917,6 +3902,9 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { } void MyAvatar::setIsInSittingState(bool isSitting) { + _sitStandStateCount = 0; + _squatCount = 0; + // on reset height we need the count to be more than one in case the user sits and stands up quickly. _isInSittingState.set(isSitting); setResetMode(true); if (isSitting) { @@ -3929,12 +3917,10 @@ void MyAvatar::setIsInSittingState(bool isSitting) { } void MyAvatar::setIsSitStandStateLocked(bool isLocked) { - const float DEFAULT_FLOOR_HEIGHT = 0.0f; _lockSitStandState.set(isLocked); _sitStandStateCount = 0; - _sumUserHeightSensorSpace = DEFAULT_AVATAR_HEIGHT; - _tippingPoint = DEFAULT_FLOOR_HEIGHT; - _averageUserHeightCount = 1; + _sumUserHeightSensorSpace = _userHeight.get(); + _tippingPoint = _userHeight.get(); if (!isLocked) { // always start the auto transition mode in standing state. setIsInSittingState(false); @@ -4238,6 +4224,9 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } } } + if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { + activate(Vertical); + } } else { if (!isActive(Rotation) && getForceActivateRotation()) { activate(Rotation); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index e8d9090e03..59f9145404 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1116,6 +1116,7 @@ public: float getSprintSpeed() const; void setSitStandStateChange(bool stateChanged); float getSitStandStateChange() const; + void updateSitStandState(float newHeightReading, float angleHeadUp); QVector getScriptUrls(); @@ -1830,10 +1831,9 @@ private: const float DEFAULT_FLOOR_HEIGHT = 0.0f; // height of user in sensor space, when standing erect. - ThreadSafeValueCache _userHeight{ DEFAULT_AVATAR_HEIGHT }; - float _sumUserHeightSensorSpace{ DEFAULT_AVATAR_HEIGHT }; - int _averageUserHeightCount{ 1 }; - bool _sitStandStateChange{ false }; + ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; + float _sumUserHeightSensorSpace { _userHeight.get() }; + bool _sitStandStateChange { false }; ThreadSafeValueCache _lockSitStandState { false }; // max unscaled forward movement speed @@ -1845,7 +1845,7 @@ private: ThreadSafeValueCache _isInSittingState { false }; int _sitStandStateCount { 0 }; int _squatCount { 0 }; - float _tippingPoint { DEFAULT_AVATAR_HEIGHT }; + float _tippingPoint { _userHeight.get() }; // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; From cf7dc49499814f286b0e19c7ad75482b9557e367 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 16 Oct 2018 16:04:31 -0700 Subject: [PATCH 120/276] removed a commment and changed the sanity check to be a const instead of a magic number --- interface/src/avatar/MyAvatar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b59ff675e1..742dda72b2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -470,8 +470,8 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { const int SITTING_COUNT_THRESHOLD = 180; const int STANDING_COUNT_THRESHOLD = 60; const int SQUATTY_COUNT_THRESHOLD = 600; + const float SITTING_UPPER_BOUND = 1.52f; - // qCDebug(interfaceapp) << "locked " << getIsSitStandStateLocked() << " away " << getIsAway() << " hmd " << qApp->isHMDMode() << " user height " << _userHeight.get(); if (!getIsSitStandStateLocked() && !getIsAway() && qApp->isHMDMode()) { if (getIsInSittingState()) { if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { @@ -494,7 +494,7 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { } } else { // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) - if (_sumUserHeightSensorSpace > 1.52f) { + if (_sumUserHeightSensorSpace > SITTING_UPPER_BOUND) { setIsInSittingState(true); } else { // tipping point is average height when sitting. From 1764531822f86b160a2e509c6e46820d9a088f1c Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 16 Oct 2018 16:31:55 -0700 Subject: [PATCH 121/276] removed hand azimuth changes, that fixed azimuth when hands go behind origin of MyAvatar, these changes will be in a separate pr --- interface/src/avatar/MyAvatar.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 742dda72b2..7a35b0ee56 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -941,13 +941,6 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { // Find the vector halfway between the hip to hand azimuth vectors // This midpoint hand azimuth is in Avatar space glm::vec2 MyAvatar::computeHandAzimuth() const { - int spine2Index = _skeletonModel->getRig().indexOfJoint("Spine2"); - glm::vec3 azimuthOrigin(0.0f,0.0f,0.0f); - if (!(spine2Index < 0)) { - // use the spine for the azimuth origin. - azimuthOrigin = getAbsoluteJointTranslationInObjectFrame(spine2Index); - } - controller::Pose leftHandPoseAvatarSpace = getLeftHandPose(); controller::Pose rightHandPoseAvatarSpace = getRightHandPose(); controller::Pose headPoseAvatarSpace = getControllerPoseInAvatarFrame(controller::Action::HEAD); @@ -955,13 +948,11 @@ glm::vec2 MyAvatar::computeHandAzimuth() const { glm::vec2 latestHipToHandController = _hipToHandController; if (leftHandPoseAvatarSpace.isValid() && rightHandPoseAvatarSpace.isValid() && headPoseAvatarSpace.isValid()) { - glm::vec3 rightHandOffset = rightHandPoseAvatarSpace.translation - azimuthOrigin; - glm::vec3 leftHandOffset = leftHandPoseAvatarSpace.translation - azimuthOrigin; // we need the old azimuth reading to prevent flipping the facing direction 180 // in the case where the hands go from being slightly less than 180 apart to slightly more than 180 apart. glm::vec2 oldAzimuthReading = _hipToHandController; - if ((glm::length(glm::vec2(rightHandOffset.x, rightHandOffset.z)) > 0.0f) && (glm::length(glm::vec2(leftHandOffset.x, leftHandOffset.z)) > 0.0f)) { - latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandOffset.x, rightHandOffset.z)), glm::normalize(glm::vec2(leftHandOffset.x, leftHandOffset.z)), HALFWAY); + if ((glm::length(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)) > 0.0f) && (glm::length(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)) > 0.0f)) { + latestHipToHandController = lerp(glm::normalize(glm::vec2(rightHandPoseAvatarSpace.translation.x, rightHandPoseAvatarSpace.translation.z)), glm::normalize(glm::vec2(leftHandPoseAvatarSpace.translation.x, leftHandPoseAvatarSpace.translation.z)), HALFWAY); } else { latestHipToHandController = glm::vec2(0.0f, -1.0f); } From baeccebfb9e3349f238adc7d4175dbcf7e1c0adb Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 16 Oct 2018 17:45:55 -0700 Subject: [PATCH 122/276] changed the transition times to make the sit longer and the stand shorter --- interface/src/avatar/MyAvatar.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7a35b0ee56..b85f19a06d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -467,8 +467,8 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { const float STANDING_HEIGHT_MULTIPLE = 1.2f; const float SITTING_HEIGHT_MULTIPLE = 0.833f; - const int SITTING_COUNT_THRESHOLD = 180; - const int STANDING_COUNT_THRESHOLD = 60; + const int SITTING_COUNT_THRESHOLD = 240; + const int STANDING_COUNT_THRESHOLD = 20; const int SQUATTY_COUNT_THRESHOLD = 600; const float SITTING_UPPER_BOUND = 1.52f; @@ -495,7 +495,7 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { } else { // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) if (_sumUserHeightSensorSpace > SITTING_UPPER_BOUND) { - setIsInSittingState(true); + setIsInSittingState(false); } else { // tipping point is average height when sitting. _tippingPoint = _sumUserHeightSensorSpace; @@ -4192,7 +4192,6 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat if (myAvatar.getHMDLeanRecenterEnabled() && qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { - if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); myAvatar.setHeadControllerFacingMovingAverage(myAvatar.getHeadControllerFacing()); From cd7af8b6052c887276e552b99be6a341530a459b Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 17 Oct 2018 08:51:40 -0700 Subject: [PATCH 123/276] changed the name of sumuserheightsensorspace to averageuserheightSensorSpace --- interface/src/avatar/MyAvatar.cpp | 16 ++++++++-------- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b85f19a06d..9fe91e44b9 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -478,7 +478,7 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { // if we recenter upwards then no longer in sitting state _sitStandStateCount++; if (_sitStandStateCount > STANDING_COUNT_THRESHOLD) { - _sumUserHeightSensorSpace = newHeightReading; + _averageUserHeightSensorSpace = newHeightReading; _tippingPoint = newHeightReading; setIsInSittingState(false); } @@ -487,18 +487,18 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state _sitStandStateCount++; if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sumUserHeightSensorSpace = newHeightReading; + _averageUserHeightSensorSpace = newHeightReading; _tippingPoint = newHeightReading; // here we stay in sit state but reset the average height setIsInSittingState(true); } } else { // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) - if (_sumUserHeightSensorSpace > SITTING_UPPER_BOUND) { + if (_averageUserHeightSensorSpace > SITTING_UPPER_BOUND) { setIsInSittingState(false); } else { // tipping point is average height when sitting. - _tippingPoint = _sumUserHeightSensorSpace; + _tippingPoint = _averageUserHeightSensorSpace; _sitStandStateCount = 0; } } @@ -507,7 +507,7 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { _sitStandStateCount++; if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { - _sumUserHeightSensorSpace = newHeightReading; + _averageUserHeightSensorSpace = newHeightReading; _tippingPoint = newHeightReading; setIsInSittingState(true); } @@ -519,7 +519,7 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { } } else { // if you are away then reset the average and set state to standing. - _sumUserHeightSensorSpace = _userHeight.get(); + _averageUserHeightSensorSpace = _userHeight.get(); _tippingPoint = _userHeight.get(); setIsInSittingState(false); } @@ -559,7 +559,7 @@ void MyAvatar::update(float deltaTime) { controller::Pose newHeightReading = getControllerPoseInSensorFrame(controller::Action::HEAD); if (newHeightReading.isValid()) { int newHeightReadingInCentimeters = glm::floor(newHeightReading.getTranslation().y * CENTIMETERS_PER_METER); - _sumUserHeightSensorSpace = lerp(_sumUserHeightSensorSpace, newHeightReading.getTranslation().y, 0.01f); + _averageUserHeightSensorSpace = lerp(_averageUserHeightSensorSpace, newHeightReading.getTranslation().y, 0.01f); _recentModeReadings.insert(newHeightReadingInCentimeters); setCurrentStandingHeight(computeStandingHeightMode(newHeightReading)); setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); @@ -3910,7 +3910,7 @@ void MyAvatar::setIsInSittingState(bool isSitting) { void MyAvatar::setIsSitStandStateLocked(bool isLocked) { _lockSitStandState.set(isLocked); _sitStandStateCount = 0; - _sumUserHeightSensorSpace = _userHeight.get(); + _averageUserHeightSensorSpace = _userHeight.get(); _tippingPoint = _userHeight.get(); if (!isLocked) { // always start the auto transition mode in standing state. diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 59f9145404..d1ba0fd9cf 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1832,7 +1832,7 @@ private: // height of user in sensor space, when standing erect. ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; - float _sumUserHeightSensorSpace { _userHeight.get() }; + float _averageUserHeightSensorSpace { _userHeight.get() }; bool _sitStandStateChange { false }; ThreadSafeValueCache _lockSitStandState { false }; From a7855dcf7061de634781b4fb92b23575ff31ba98 Mon Sep 17 00:00:00 2001 From: Clement Date: Wed, 17 Oct 2018 15:09:38 -0700 Subject: [PATCH 124/276] Only track stats for compatible packets --- interface/src/octree/OctreePacketProcessor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 0d991fc9bc..5c8868abdb 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -91,7 +91,9 @@ void OctreePacketProcessor::processPacket(QSharedPointer messag return; // bail since piggyback version doesn't match } - qApp->trackIncomingOctreePacket(*message, sendingNode, wasStatsPacket); + if (packetType != PacketType::EntityQueryInitialResultsComplete) { + qApp->trackIncomingOctreePacket(*message, sendingNode, wasStatsPacket); + } // seek back to beginning of packet after tracking message->seek(0); From 9cd3c35cc6858473c012e4f380b7d92f9a4381e1 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 17 Oct 2018 18:18:51 -0700 Subject: [PATCH 125/276] style changes and fix hidden top row --- scripts/system/html/css/edit-style.css | 58 ++++++++++++++------------ scripts/system/html/entityList.html | 28 ++++++------- scripts/system/html/js/entityList.js | 7 +++- 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 088b0952ae..1b0094cfb7 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -273,7 +273,6 @@ input[type="number"] { height: 28px; width: 124px; } - input[type=number] { padding-right: 3px; } @@ -1085,66 +1084,68 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { bottom: 0; } +#filter-type-selectBox select { + border-radius: 14.5px; +} #filter-type-checkboxes { position: absolute; z-index: 2; - top: 28px; + top: 48px; display: none; border: none; } #filter-type-checkboxes div { position: relative; - height: 25px; + height: 22px; } #filter-type-checkboxes span { position: relative; top: 3px; font-family: hifi-glyphs; - font-size: 16px; - color: #404040; - padding-left: 12px; - padding-right: 12px; + font-size: 13px; + color: #000000; + padding-left: 6px; + padding-right: 4px; } #filter-type-checkboxes label { - position: relative; - top: -13px; - z-index: 3; + position: absolute; + top: -20px; + z-index: 2; display: block; font-family: FiraSans-SemiBold; - color: #404040; + font-size: 11px; + color: #000000; background-color: #afafaf; + width: 200px; + height: 22px; padding-top: 1px; - padding-right: 12px; - height: 24px; } #filter-type-checkboxes label:hover { background-color: #1e90ff; } -#filter-type-checkboxes input[type=checkbox] { - position: relative; - top: 6px; - right: -10px; - z-index: 4; - display: block; -} #filter-type-checkboxes input[type=checkbox] + label { - background-image: none; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADUOYnF4LQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIMSURBVFjD7ZmxkqowFIZ/7mwJPen1AezV3t6hFvrQhweAHvrQ8wL2xt4HwD7ppd+tvHOvu0gCYdEZTsmAfpNzzpcTcAB84o3iD94sZuCx4+Pxwvl8dl4JcL1ef84lMQPPwBZDSgkp5XsASylBKUUYhhBCvDbw7XYDpRRKKTRNA8YYOOevC5ymKZRS/13jnHdCTwLMOW8tAc45GGNomuY1gKuq6lxFIQQopdMDXy4X5HmudW8URdMCSynBGNOG3Ww20wHf9dVWl4+wbav7a8CMsW9G+Cm22+1T2F8BzvMc1+u18z5CCJIkseNhKSX2+z2qqjLWl84zhBAURQHXde0A31Oa57nWbqSrLwDwPA9FUcD3fTtb82NKu8QOAHVda+srSRJt2E7gtpQKIXA4HH6csmzpyxj4dDo9TalSCpRS1HX9TV86RujSlxGwlBJpmnY+rJRCGIZ/s2BTX9qnZgBwHAee52mJ/l7nx+PRqr6MVtj3fZRlaVRf/5aGDX0Z17DrusiyrHfqhuqrt9aiKEIcx4OBTfU1aOMIggBlWYIQ0utP+uhr8CyxXC5RFIUxdBAE1srKePgxbcbVamWlnAZNa7rNSAhBlmWv8yLlWTPa0Nco83BbM2ZZZsUIowzwj80YxzEWi8VoB4IPGz9yb0YhBHa73agnGGtHJNd1R4ed9FVV33Awf6ebgd8b+Av9A/rq6s3hjgAAAABJRU5ErkJggg=='); + background-size: 11px 11px; + background-position: top 5px left 14px; } #filter-type-checkboxes input[type=checkbox]:checked + label { - background-image: none; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH4goSADMveELP9QAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAIqSURBVFjD7ZmxkqowFIb/7GwJPfT6APZib+9QC33o4QGghz70vIC9sfcBsE966bPNWlxnlQTDRWc4JUT4hpPz5SQSAAofFF/4sJiBx47v+wun04m8E+B6vVbzlJiBZ2CLIYRQQgj1EcBCCEUpRRRF4Jyrtwa+Xq+glEJKia7rkKYpGGPqbYHzPFdSyn+uMcZ6oScBZowpzvmje0jTVHVd9x7ATdMoxtjTMZxzUErV5MDn81mVZak1No7jab+wEEKlaaoNGwQBmQz4pq9H8/IeNo5jMmnRpWmKeyP8FZvN5insfwEuy1JdLpfecb7vI8uy3tb2Szelu91ONU1jtP9jjKmmabRgq6qC4zh2VrpbSsuy1FqNdPUFAK7roqoqeJ6ntXH4Mk1pn9gBoG1bbX1lWaYN2wv8KKWcc+z3+z+7LFv6MgY+Ho9PUyqlBKUUbduqe33pGKFPX0bAQgiV53nvj6WUiKIIt2K0qS/tXTMAEELguq6W6H/nOQ6Hg1V9GX1hz/NIXdckCALtB7Vta1VfxnPYcRwURUEeNSGmYaqvwVqL45gkSfIysKm+Xlo4wjAkdV3D9/1BLxmir5d7ieVySaqqMoYOw3CwEV5ufkyLcbVaIUkSq2d1xt2abjH6vo+iKKwfLA5uL58Vow19jdIPPyrGoiisGGGUBv6+GJMkwWKxGO2M+dvGQ36LEZxztd1uRz0Qt7ZFchwHY8NOelQ1NAjm/+lm4M8G/gH2zx33BSr7jAAAAABJRU5ErkJggg=='); + background-size: 11px 11px; + background-position: top 5px left 14px; } #filter-type-checkboxes input[type=checkbox]:hover + label { background-color: #1e90ff; } #filter-search-and-icon { - position: absolute; - left: 120px; - width: calc(100% - 300px); + position: relative; + left: 118px; + width: calc(100% - 126px); } #filter-in-view { position: absolute; + top: 0px; right: 126px; } @@ -1152,13 +1153,18 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; float: right; margin-right: -168px; - top: -17px; + top: -45px; } #filter-radius-and-unit label { margin-left: 2px; } #filter-radius-and-unit input { width: 120px; + border-radius: 14.5px; + font-style: italic; +} +#filter-radius-and-unit input[type=number]::-webkit-inner-spin-button { + display: none; } #entity-table-scroll { diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 8371e2cbd7..dc022c9ab9 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -30,20 +30,20 @@
-
-
- -
-
-
- -
-
-
- Y -
+
+
+ +
+
+
+ +
+
+
+ Y +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 400cdc7dce..66ad08e27c 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -23,6 +23,7 @@ const FILTER_IN_VIEW_ATTRIBUTE = "pressed"; const WINDOW_NONVARIABLE_HEIGHT = 227; const NUM_COLUMNS = 12; const EMPTY_ENTITY_ID = "0"; +const MAX_LENGTH_RADIUS = 9; const DELETE = 46; // Key code for the delete key. const KEY_P = 80; // Key code for letter p used for Parenting hotkey. @@ -203,7 +204,11 @@ function loaded() { elFilterSearch.onsearch = refreshEntityList; elFilterInView.onclick = toggleFilterInView; elFilterRadius.onchange = onRadiusChange; - elInfoToggle.onclick = toggleInfo; + elFilterRadius.oninput = function(event) { + if (event.target.value.length > MAX_LENGTH_RADIUS) { + event.target.value = event.target.value.slice(0, MAX_LENGTH_RADIUS); + } + } // create filter type dropdown checkboxes with label and icon for each type elFilterTypeSelectBox.onclick = toggleTypeDropdown; From 9e35af0c4a3ff246d3fe32410c9b67952f22e265 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 17 Oct 2018 18:24:09 -0700 Subject: [PATCH 126/276] remove space --- scripts/system/html/entityList.html | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index dc022c9ab9..434f8a5f87 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -100,7 +100,6 @@ -
There are no entities to display. Please check your filters or create an entity to begin.
From 684f7d5689b78def61ca3e9f6c035297669e6fe1 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 3 Oct 2018 19:35:03 +0200 Subject: [PATCH 127/276] move export selection button to bottom of the list --- scripts/system/html/css/edit-style.css | 9 +++++++++ scripts/system/html/entityList.html | 13 ++++++++++--- scripts/system/html/js/entityList.js | 18 ++++++------------ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8d334609a6..fa64960487 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1063,6 +1063,15 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; /* New positioning context. */ } +#footer-text { + float: right; + margin-right: 0; +} + +#entity-list-footer { + padding-top: 38px; +} + #search-area { padding-right: 168px; padding-bottom: 24px; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 7eed78ecf3..cc7afe3bdb 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -1,4 +1,4 @@ -
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index fcc457eb7f..060ed1f67a 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1059,13 +1059,13 @@ const GROUPS = [ { label: "Grabbable", type: "bool", - propertyID: "grabbable", + propertyID: "grab.grabbable", column: 1, }, { label: "Triggerable", type: "bool", - propertyID: "triggerable", + propertyID: "grab.triggerable", column: 2, }, { @@ -1075,9 +1075,9 @@ const GROUPS = [ column: 1, }, { - label: "Ignore inverse kinematics", + label: "Follow Controller", type: "bool", - propertyID: "ignoreIK", + propertyID: "grab.grabFollowsController", column: 2, }, { @@ -2767,7 +2767,7 @@ function loaded() { showUserDataTextArea(); showSaveUserDataButton(); showNewJSONEditorButton(); - + deleteJSONMaterialEditor(); getPropertyInputElement("materialData").value = ""; showMaterialDataTextArea(); @@ -2937,46 +2937,6 @@ function loaded() { } } - let elGrabbable = getPropertyInputElement("grabbable"); - let elTriggerable = getPropertyInputElement("triggerable"); - let elIgnoreIK = getPropertyInputElement("ignoreIK"); - elGrabbable.checked = getPropertyInputElement("dynamic").checked; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - let grabbablesSet = false; - let parsedUserData = {}; - try { - parsedUserData = JSON.parse(selectedEntityProperties.userData); - if ("grabbableKey" in parsedUserData) { - grabbablesSet = true; - let grabbableData = parsedUserData.grabbableKey; - if ("grabbable" in grabbableData) { - elGrabbable.checked = grabbableData.grabbable; - } else { - elGrabbable.checked = true; - } - if ("triggerable" in grabbableData) { - elTriggerable.checked = grabbableData.triggerable; - } else if ("wantsTrigger" in grabbableData) { - elTriggerable.checked = grabbableData.wantsTrigger; - } else { - elTriggerable.checked = false; - } - if ("ignoreIK" in grabbableData) { - elIgnoreIK.checked = grabbableData.ignoreIK; - } else { - elIgnoreIK.checked = true; - } - } - } catch (e) { - // TODO: What should go here? - } - if (!grabbablesSet) { - elGrabbable.checked = true; - elTriggerable.checked = false; - elIgnoreIK.checked = true; - } - if (selectedEntityProperties.type === "Image") { let imageLink = JSON.parse(selectedEntityProperties.textures)["tex.picture"]; getPropertyInputElement("image").value = imageLink; @@ -3119,20 +3079,6 @@ function loaded() { elDiv.insertBefore(elStaticMaterialData, elMaterialData); elDiv.insertBefore(elMaterialDataEditor, elMaterialData); - // User Data Fields - let elGrabbable = getPropertyInputElement("grabbable"); - let elTriggerable = getPropertyInputElement("triggerable"); - let elIgnoreIK = getPropertyInputElement("ignoreIK"); - elGrabbable.addEventListener('change', function() { - userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, true); - }); - elTriggerable.addEventListener('change', function() { - userDataChanger("grabbableKey", "triggerable", elTriggerable, elUserData, false, ['wantsTrigger']); - }); - elIgnoreIK.addEventListener('change', function() { - userDataChanger("grabbableKey", "ignoreIK", elIgnoreIK, elUserData, true); - }); - // Special Property Callbacks let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); From 740123b8531a0a7d24215051a373e8fac44b738d Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Fri, 15 Jun 2018 21:44:12 +0300 Subject: [PATCH 134/276] rename stylues-uit => stylesUit & controls-uit => controlsUit note: the idea is to make imports like 'import controlsUit 1.0' to work with 'styles-uit'/'controls-uit' it is not possible because of two reasons: 1. import controls-uit 1.0 is invalid syntax 2. qmldir inside controls-uit is 'module controlsUit' --- interface/resources/qml/AudioScopeUI.qml | 4 ++-- interface/resources/qml/Browser.qml | 4 ++-- interface/resources/qml/CurrentAPI.qml | 4 ++-- interface/resources/qml/InfoView.qml | 2 +- interface/resources/qml/LoginDialog.qml | 4 ++-- .../qml/LoginDialog/+android/LinkAccountBody.qml | 4 ++-- .../resources/qml/LoginDialog/+android/SignUpBody.qml | 4 ++-- .../resources/qml/LoginDialog/CompleteProfileBody.qml | 4 ++-- .../resources/qml/LoginDialog/LinkAccountBody.qml | 5 +++-- interface/resources/qml/LoginDialog/SignInBody.qml | 4 ++-- interface/resources/qml/LoginDialog/SignUpBody.qml | 4 ++-- .../qml/LoginDialog/UsernameCollisionBody.qml | 4 ++-- interface/resources/qml/LoginDialog/WelcomeBody.qml | 4 ++-- interface/resources/qml/QmlWebWindow.qml | 4 ++-- interface/resources/qml/QmlWindow.qml | 4 ++-- interface/resources/qml/TabletBrowser.qml | 4 ++-- interface/resources/qml/UpdateDialog.qml | 4 ++-- interface/resources/qml/controls/Button.qml | 1 - .../resources/qml/controls/FlickableWebViewCore.qml | 2 +- interface/resources/qml/controls/TabletWebButton.qml | 2 +- interface/resources/qml/controls/TabletWebScreen.qml | 2 +- interface/resources/qml/controls/TabletWebView.qml | 4 ++-- interface/resources/qml/controls/WebView.qml | 2 +- .../+android/ImageButton.qml | 6 +++--- .../AttachmentsTable.qml | 4 ++-- .../qml/{controls-uit => controlsUit}/BaseWebView.qml | 0 .../qml/{controls-uit => controlsUit}/Button.qml | 2 +- .../qml/{controls-uit => controlsUit}/CheckBox.qml | 2 +- .../{controls-uit => controlsUit}/CheckBoxQQC2.qml | 4 ++-- .../qml/{controls-uit => controlsUit}/ComboBox.qml | 4 ++-- .../{controls-uit => controlsUit}/ContentSection.qml | 2 +- .../qml/{controls-uit => controlsUit}/FilterBar.qml | 4 ++-- .../qml/{controls-uit => controlsUit}/GlyphButton.qml | 2 +- .../{controls-uit => controlsUit}/HorizontalRule.qml | 0 .../HorizontalSpacer.qml | 2 +- .../{controls-uit => controlsUit}/ImageMessageBox.qml | 2 +- .../qml/{controls-uit => controlsUit}/Key.qml | 0 .../qml/{controls-uit => controlsUit}/Keyboard.qml | 0 .../qml/{controls-uit => controlsUit}/Label.qml | 2 +- .../{controls-uit => controlsUit}/QueuedButton.qml | 2 +- .../qml/{controls-uit => controlsUit}/RadioButton.qml | 4 ++-- .../qml/{controls-uit => controlsUit}/ScrollBar.qml | 2 +- .../qml/{controls-uit => controlsUit}/Separator.qml | 2 +- .../qml/{controls-uit => controlsUit}/Slider.qml | 4 ++-- .../qml/{controls-uit => controlsUit}/SpinBox.qml | 4 ++-- .../qml/{controls-uit => controlsUit}/Switch.qml | 2 +- .../qml/{controls-uit => controlsUit}/Table.qml | 2 +- .../TabletContentSection.qml | 2 +- .../{controls-uit => controlsUit}/TabletHeader.qml | 2 +- .../qml/{controls-uit => controlsUit}/TextAction.qml | 4 ++-- .../qml/{controls-uit => controlsUit}/TextEdit.qml | 2 +- .../qml/{controls-uit => controlsUit}/TextField.qml | 4 ++-- .../qml/{controls-uit => controlsUit}/ToolTip.qml | 0 .../qml/{controls-uit => controlsUit}/Tree.qml | 2 +- .../{controls-uit => controlsUit}/VerticalSpacer.qml | 2 +- .../{controls-uit => controlsUit}/WebGlyphButton.qml | 2 +- .../qml/{controls-uit => controlsUit}/WebSpinner.qml | 0 .../qml/{controls-uit => controlsUit}/WebView.qml | 0 .../qml/{controls-uit => controlsUit}/qmldir | 0 interface/resources/qml/dialogs/AssetDialog.qml | 2 +- interface/resources/qml/dialogs/CustomQueryDialog.qml | 4 ++-- interface/resources/qml/dialogs/FileDialog.qml | 4 ++-- interface/resources/qml/dialogs/MessageDialog.qml | 4 ++-- interface/resources/qml/dialogs/PreferencesDialog.qml | 4 ++-- interface/resources/qml/dialogs/QueryDialog.qml | 4 ++-- interface/resources/qml/dialogs/TabletAssetDialog.qml | 2 +- .../resources/qml/dialogs/TabletCustomQueryDialog.qml | 4 ++-- interface/resources/qml/dialogs/TabletFileDialog.qml | 4 ++-- interface/resources/qml/dialogs/TabletLoginDialog.qml | 4 ++-- interface/resources/qml/dialogs/TabletMessageBox.qml | 4 ++-- interface/resources/qml/dialogs/TabletQueryDialog.qml | 4 ++-- .../qml/dialogs/assetDialog/AssetDialogContent.qml | 4 ++-- .../qml/dialogs/fileDialog/FileTypeSelection.qml | 2 +- .../qml/dialogs/messageDialog/MessageDialogButton.qml | 2 +- .../qml/dialogs/preferences/AvatarPreference.qml | 2 +- .../qml/dialogs/preferences/BrowsablePreference.qml | 2 +- .../qml/dialogs/preferences/ButtonPreference.qml | 2 +- .../qml/dialogs/preferences/CheckBoxPreference.qml | 2 +- .../qml/dialogs/preferences/ComboBoxPreference.qml | 4 ++-- .../qml/dialogs/preferences/EditablePreference.qml | 2 +- .../qml/dialogs/preferences/PrimaryHandPreference.qml | 2 +- .../dialogs/preferences/RadioButtonsPreference.qml | 4 ++-- .../resources/qml/dialogs/preferences/Section.qml | 4 ++-- .../qml/dialogs/preferences/SliderPreference.qml | 2 +- .../qml/dialogs/preferences/SpinBoxPreference.qml | 2 +- .../dialogs/preferences/SpinnerSliderPreference.qml | 2 +- interface/resources/qml/hifi/+android/ActionBar.qml | 4 ++-- interface/resources/qml/hifi/+android/AudioBar.qml | 4 ++-- .../resources/qml/hifi/+android/AvatarOption.qml | 2 +- interface/resources/qml/hifi/+android/StatsBar.qml | 4 ++-- .../resources/qml/hifi/+android/WindowHeader.qml | 4 ++-- .../resources/qml/hifi/+android/bottomHudOptions.qml | 4 ++-- interface/resources/qml/hifi/+android/modesbar.qml | 4 ++-- interface/resources/qml/hifi/AssetServer.qml | 4 ++-- interface/resources/qml/hifi/Card.qml | 2 +- interface/resources/qml/hifi/ComboDialog.qml | 4 ++-- interface/resources/qml/hifi/Desktop.qml | 2 +- .../resources/qml/hifi/DesktopLetterboxMessage.qml | 2 +- interface/resources/qml/hifi/Feed.qml | 2 +- interface/resources/qml/hifi/LetterboxMessage.qml | 2 +- interface/resources/qml/hifi/NameCard.qml | 4 ++-- interface/resources/qml/hifi/Pal.qml | 4 ++-- interface/resources/qml/hifi/SkyboxChanger.qml | 4 ++-- interface/resources/qml/hifi/SpectatorCamera.qml | 4 ++-- interface/resources/qml/hifi/TabletTextButton.qml | 2 +- interface/resources/qml/hifi/TextButton.qml | 2 +- interface/resources/qml/hifi/WebBrowser.qml | 4 ++-- interface/resources/qml/hifi/audio/Audio.qml | 4 ++-- interface/resources/qml/hifi/audio/AudioTabButton.qml | 4 ++-- interface/resources/qml/hifi/audio/CheckBox.qml | 2 +- .../resources/qml/hifi/audio/PlaySampleSound.qml | 4 ++-- .../resources/qml/hifi/commerce/checkout/Checkout.qml | 4 ++-- .../qml/hifi/commerce/common/CommerceLightbox.qml | 4 ++-- .../commerce/common/EmulatedMarketplaceHeader.qml | 4 ++-- .../qml/hifi/commerce/common/FirstUseTutorial.qml | 4 ++-- .../hifi/commerce/common/sendAsset/ConnectionItem.qml | 4 ++-- .../commerce/common/sendAsset/RecipientDisplay.qml | 4 ++-- .../qml/hifi/commerce/common/sendAsset/SendAsset.qml | 4 ++-- .../inspectionCertificate/InspectionCertificate.qml | 4 ++-- .../qml/hifi/commerce/purchases/PurchasedItem.qml | 4 ++-- .../qml/hifi/commerce/purchases/Purchases.qml | 4 ++-- interface/resources/qml/hifi/commerce/wallet/Help.qml | 4 ++-- .../resources/qml/hifi/commerce/wallet/NeedsLogIn.qml | 4 ++-- .../qml/hifi/commerce/wallet/PassphraseChange.qml | 4 ++-- .../qml/hifi/commerce/wallet/PassphraseModal.qml | 4 ++-- .../qml/hifi/commerce/wallet/PassphraseSelection.qml | 4 ++-- .../resources/qml/hifi/commerce/wallet/Security.qml | 4 ++-- .../qml/hifi/commerce/wallet/SecurityImageChange.qml | 4 ++-- .../hifi/commerce/wallet/SecurityImageSelection.qml | 4 ++-- .../resources/qml/hifi/commerce/wallet/Wallet.qml | 4 ++-- .../qml/hifi/commerce/wallet/WalletChoice.qml | 4 ++-- .../resources/qml/hifi/commerce/wallet/WalletHome.qml | 4 ++-- .../qml/hifi/commerce/wallet/WalletSetup.qml | 4 ++-- interface/resources/qml/hifi/dialogs/AboutDialog.qml | 2 +- .../resources/qml/hifi/dialogs/RunningScripts.qml | 4 ++-- .../resources/qml/hifi/dialogs/TabletAboutDialog.qml | 2 +- .../resources/qml/hifi/dialogs/TabletAssetServer.qml | 4 ++-- .../resources/qml/hifi/dialogs/TabletDCDialog.qml | 4 ++-- .../resources/qml/hifi/dialogs/TabletDebugWindow.qml | 4 ++-- .../qml/hifi/dialogs/TabletEntityStatistics.qml | 4 ++-- .../qml/hifi/dialogs/TabletEntityStatisticsItem.qml | 4 ++-- .../resources/qml/hifi/dialogs/TabletLODTools.qml | 4 ++-- .../qml/hifi/dialogs/TabletRunningScripts.qml | 4 ++-- .../qml/hifi/dialogs/content/ModelBrowserContent.qml | 2 +- .../resources/qml/hifi/tablet/CalibratingScreen.qml | 4 ++-- .../resources/qml/hifi/tablet/ControllerSettings.qml | 4 ++-- interface/resources/qml/hifi/tablet/EditTabButton.qml | 4 ++-- interface/resources/qml/hifi/tablet/EditTabView.qml | 4 ++-- interface/resources/qml/hifi/tablet/InputRecorder.qml | 4 ++-- .../resources/qml/hifi/tablet/NewMaterialDialog.qml | 4 ++-- .../resources/qml/hifi/tablet/NewModelDialog.qml | 4 ++-- .../resources/qml/hifi/tablet/OpenVrConfiguration.qml | 4 ++-- .../resources/qml/hifi/tablet/TabletAddressDialog.qml | 4 ++-- interface/resources/qml/hifi/tablet/TabletHome.qml | 2 +- interface/resources/qml/hifi/tablet/TabletMenu.qml | 2 +- .../resources/qml/hifi/tablet/TabletMenuItem.qml | 4 ++-- .../resources/qml/hifi/tablet/TabletMenuView.qml | 2 +- .../qml/hifi/tablet/TabletModelBrowserDialog.qml | 4 ++-- .../hifi/tablet/tabletWindows/TabletFileDialog.qml | 4 ++-- .../tablet/tabletWindows/TabletPreferencesDialog.qml | 4 ++-- .../hifi/tablet/tabletWindows/preferences/Section.qml | 4 ++-- .../preferences/TabletBrowsablePreference.qml | 2 +- .../+android/HifiConstants.qml | 0 .../{styles-uit => stylesUit}/AnonymousProRegular.qml | 0 .../qml/{styles-uit => stylesUit}/ButtonLabel.qml | 0 .../qml/{styles-uit => stylesUit}/FiraSansRegular.qml | 0 .../{styles-uit => stylesUit}/FiraSansSemiBold.qml | 0 .../qml/{styles-uit => stylesUit}/HiFiGlyphs.qml | 0 .../qml/{styles-uit => stylesUit}/HifiConstants.qml | 0 .../qml/{styles-uit => stylesUit}/IconButton.qml | 0 .../qml/{styles-uit => stylesUit}/InfoItem.qml | 0 .../qml/{styles-uit => stylesUit}/InputLabel.qml | 0 .../qml/{styles-uit => stylesUit}/ListItem.qml | 0 .../resources/qml/{styles-uit => stylesUit}/Logs.qml | 0 .../qml/{styles-uit => stylesUit}/OverlayTitle.qml | 0 .../qml/{styles-uit => stylesUit}/RalewayBold.qml | 0 .../qml/{styles-uit => stylesUit}/RalewayLight.qml | 0 .../qml/{styles-uit => stylesUit}/RalewayRegular.qml | 0 .../qml/{styles-uit => stylesUit}/RalewaySemiBold.qml | 0 .../qml/{styles-uit => stylesUit}/SectionName.qml | 0 .../qml/{styles-uit => stylesUit}/Separator.qml | 2 +- .../qml/{styles-uit => stylesUit}/ShortcutText.qml | 0 .../qml/{styles-uit => stylesUit}/TabName.qml | 0 .../qml/{styles-uit => stylesUit}/TextFieldInput.qml | 0 .../resources/qml/{styles-uit => stylesUit}/qmldir | 0 interface/resources/qml/windows/Decoration.qml | 2 +- interface/resources/qml/windows/DefaultFrame.qml | 2 +- .../resources/qml/windows/DefaultFrameDecoration.qml | 2 +- interface/resources/qml/windows/Fadable.qml | 2 +- interface/resources/qml/windows/Frame.qml | 2 +- interface/resources/qml/windows/ModalFrame.qml | 4 ++-- interface/resources/qml/windows/ScrollingWindow.qml | 4 ++-- interface/resources/qml/windows/TabletModalFrame.qml | 4 ++-- interface/resources/qml/windows/ToolFrame.qml | 2 +- .../resources/qml/windows/ToolFrameDecoration.qml | 2 +- interface/resources/qml/windows/Window.qml | 2 +- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 1 + scripts/developer/tests/ControlsGallery.qml | 11 ++--------- 198 files changed, 274 insertions(+), 280 deletions(-) rename interface/resources/qml/{controls-uit => controlsUit}/+android/ImageButton.qml (96%) rename interface/resources/qml/{controls-uit => controlsUit}/AttachmentsTable.qml (98%) rename interface/resources/qml/{controls-uit => controlsUit}/BaseWebView.qml (100%) rename interface/resources/qml/{controls-uit => controlsUit}/Button.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/CheckBox.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/CheckBoxQQC2.qml (98%) rename interface/resources/qml/{controls-uit => controlsUit}/ComboBox.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/ContentSection.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/FilterBar.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/GlyphButton.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/HorizontalRule.qml (100%) rename interface/resources/qml/{controls-uit => controlsUit}/HorizontalSpacer.qml (94%) rename interface/resources/qml/{controls-uit => controlsUit}/ImageMessageBox.qml (98%) rename interface/resources/qml/{controls-uit => controlsUit}/Key.qml (100%) rename interface/resources/qml/{controls-uit => controlsUit}/Keyboard.qml (100%) rename interface/resources/qml/{controls-uit => controlsUit}/Label.qml (97%) rename interface/resources/qml/{controls-uit => controlsUit}/QueuedButton.qml (98%) rename interface/resources/qml/{controls-uit => controlsUit}/RadioButton.qml (97%) rename interface/resources/qml/{controls-uit => controlsUit}/ScrollBar.qml (98%) rename interface/resources/qml/{controls-uit => controlsUit}/Separator.qml (98%) rename interface/resources/qml/{controls-uit => controlsUit}/Slider.qml (98%) rename interface/resources/qml/{controls-uit => controlsUit}/SpinBox.qml (98%) rename interface/resources/qml/{controls-uit => controlsUit}/Switch.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/Table.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/TabletContentSection.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/TabletHeader.qml (96%) rename interface/resources/qml/{controls-uit => controlsUit}/TextAction.qml (96%) rename interface/resources/qml/{controls-uit => controlsUit}/TextEdit.qml (95%) rename interface/resources/qml/{controls-uit => controlsUit}/TextField.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/ToolTip.qml (100%) rename interface/resources/qml/{controls-uit => controlsUit}/Tree.qml (99%) rename interface/resources/qml/{controls-uit => controlsUit}/VerticalSpacer.qml (94%) rename interface/resources/qml/{controls-uit => controlsUit}/WebGlyphButton.qml (98%) rename interface/resources/qml/{controls-uit => controlsUit}/WebSpinner.qml (100%) rename interface/resources/qml/{controls-uit => controlsUit}/WebView.qml (100%) rename interface/resources/qml/{controls-uit => controlsUit}/qmldir (100%) rename interface/resources/qml/{styles-uit => stylesUit}/+android/HifiConstants.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/AnonymousProRegular.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/ButtonLabel.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/FiraSansRegular.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/FiraSansSemiBold.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/HiFiGlyphs.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/HifiConstants.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/IconButton.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/InfoItem.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/InputLabel.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/ListItem.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/Logs.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/OverlayTitle.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/RalewayBold.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/RalewayLight.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/RalewayRegular.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/RalewaySemiBold.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/SectionName.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/Separator.qml (97%) rename interface/resources/qml/{styles-uit => stylesUit}/ShortcutText.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/TabName.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/TextFieldInput.qml (100%) rename interface/resources/qml/{styles-uit => stylesUit}/qmldir (100%) diff --git a/interface/resources/qml/AudioScopeUI.qml b/interface/resources/qml/AudioScopeUI.qml index aa181dbf8d..91908807e2 100644 --- a/interface/resources/qml/AudioScopeUI.qml +++ b/interface/resources/qml/AudioScopeUI.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import "styles-uit" -import "controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit Item { id: root diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 4474cfb2cd..01de7a36f9 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -2,9 +2,9 @@ import QtQuick 2.5 import QtWebChannel 1.0 import QtWebEngine 1.5 -import "controls-uit" +import controlsUit 1.0 import "styles" as HifiStyles -import "styles-uit" +import stylesUit 1.0 import "windows" ScrollingWindow { diff --git a/interface/resources/qml/CurrentAPI.qml b/interface/resources/qml/CurrentAPI.qml index 96bfb5c36b..4ea45041c3 100644 --- a/interface/resources/qml/CurrentAPI.qml +++ b/interface/resources/qml/CurrentAPI.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import "styles-uit" -import "controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { id: root diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index f18969fb2f..8c5900b4c3 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import Hifi 1.0 as Hifi -import "controls-uit" +import controlsUit 1.0 import "windows" as Windows Windows.ScrollingWindow { diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 336858502d..12117aaba4 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -11,8 +11,8 @@ import Hifi 1.0 import QtQuick 2.4 -import "controls-uit" -import "styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "windows" import "LoginDialog" diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index 96b638c911..a40110b1e9 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -13,8 +13,8 @@ import QtQuick 2.4 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: linkAccountBody diff --git a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml index 3a44a8d741..10909e4c85 100644 --- a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml @@ -13,8 +13,8 @@ import QtQuick 2.4 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: signupBody diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml index fe4c511f1d..3a57061de4 100644 --- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml +++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: completeProfileBody diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 48cf124127..7951d45c0e 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -13,8 +13,9 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 + Item { id: linkAccountBody clip: true diff --git a/interface/resources/qml/LoginDialog/SignInBody.qml b/interface/resources/qml/LoginDialog/SignInBody.qml index 9cb1add704..7fe29e13f6 100644 --- a/interface/resources/qml/LoginDialog/SignInBody.qml +++ b/interface/resources/qml/LoginDialog/SignInBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: signInBody diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index bb30696e4c..d3c898d76f 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls 1.4 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: signupBody diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index bf05a36ce1..2a41353534 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.4 import QtQuick.Controls 1.4 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: usernameCollisionBody diff --git a/interface/resources/qml/LoginDialog/WelcomeBody.qml b/interface/resources/qml/LoginDialog/WelcomeBody.qml index 551ec263b7..020e6db002 100644 --- a/interface/resources/qml/LoginDialog/WelcomeBody.qml +++ b/interface/resources/qml/LoginDialog/WelcomeBody.qml @@ -11,8 +11,8 @@ import Hifi 1.0 import QtQuick 2.4 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: welcomeBody diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 8c4d6145ec..322535641d 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -13,8 +13,8 @@ import QtWebEngine 1.1 import QtWebChannel 1.0 import "windows" as Windows -import "controls-uit" as Controls -import "styles-uit" +import controlsUit 1.0 as Controls +import stylesUit 1.0 Windows.ScrollingWindow { id: root diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index bef6423e25..53e6bcc37d 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -2,9 +2,9 @@ import QtQuick 2.3 import "windows" as Windows import "controls" -import "controls-uit" as Controls +import controlsUit 1.0 as Controls import "styles" -import "styles-uit" +import stylesUit 1.0 Windows.Window { id: root diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index 141c1f25a7..720a904231 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -3,9 +3,9 @@ import QtWebChannel 1.0 import QtWebEngine 1.5 import "controls" -import "controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls import "styles" as HifiStyles -import "styles-uit" +import stylesUit 1.0 import "windows" Item { diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index 5e05601ce4..9c22d0b65b 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -4,9 +4,9 @@ import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtGraphicalEffects 1.0 -import "controls-uit" +import controlsUit 1.0 import "styles" as HifiStyles -import "styles-uit" +import stylesUit 1.0 import "windows" ScrollingWindow { diff --git a/interface/resources/qml/controls/Button.qml b/interface/resources/qml/controls/Button.qml index 6cbdec5644..b677822c0e 100644 --- a/interface/resources/qml/controls/Button.qml +++ b/interface/resources/qml/controls/Button.qml @@ -3,7 +3,6 @@ import QtQuick.Controls 2.2 as Original import "." import "../styles" -import "../controls-uit" Original.Button { id: control diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 943f15e1de..cce32c137a 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -4,7 +4,7 @@ import QtWebChannel 1.0 import QtQuick.Controls 2.2 -import "../styles-uit" as StylesUIt +import stylesUit 1.0 as StylesUIt Item { id: flick diff --git a/interface/resources/qml/controls/TabletWebButton.qml b/interface/resources/qml/controls/TabletWebButton.qml index d016f71f2d..140461d817 100644 --- a/interface/resources/qml/controls/TabletWebButton.qml +++ b/interface/resources/qml/controls/TabletWebButton.qml @@ -10,7 +10,7 @@ import Hifi 1.0 import QtQuick 2.4 -import "../styles-uit" +import stylesUit 1.0 Rectangle { property alias text: label.text diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml index bb037ad478..be11f16498 100644 --- a/interface/resources/qml/controls/TabletWebScreen.qml +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -1,5 +1,5 @@ import QtQuick 2.7 -import "../controls-uit" as HiFiControls +import controlsUit 1.0 as HiFiControls Item { id: root diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index db695dbfb2..0c5ca37e00 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -1,8 +1,8 @@ import QtQuick 2.7 import QtWebEngine 1.5 -import "../controls-uit" as HiFiControls +import controlsUit 1.0 as HiFiControls import "../styles" as HifiStyles -import "../styles-uit" +import stylesUit 1.0 Item { id: root diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 71bf69fdc8..375bcd50e0 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -1,5 +1,5 @@ import QtQuick 2.7 -import "../controls-uit" as HiFiControls +import controlsUit 1.0 as HiFiControls Item { width: parent !== null ? parent.width : undefined diff --git a/interface/resources/qml/controls-uit/+android/ImageButton.qml b/interface/resources/qml/controlsUit/+android/ImageButton.qml similarity index 96% rename from interface/resources/qml/controls-uit/+android/ImageButton.qml rename to interface/resources/qml/controlsUit/+android/ImageButton.qml index 5ebf7cd3e9..88eaf95d76 100644 --- a/interface/resources/qml/controls-uit/+android/ImageButton.qml +++ b/interface/resources/qml/controlsUit/+android/ImageButton.qml @@ -1,6 +1,6 @@ // // ImageButton.qml -// interface/resources/qml/controls-uit +// interface/resources/qml/controlsUit // // Created by Gabriel Calero & Cristian Duarte on 12 Oct 2017 // Copyright 2017 High Fidelity, Inc. @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Layouts 1.3 -import "../styles-uit" as HifiStyles +import "../stylesUit" as HifiStyles Item { id: button @@ -79,4 +79,4 @@ Item { } } ] -} \ No newline at end of file +} diff --git a/interface/resources/qml/controls-uit/AttachmentsTable.qml b/interface/resources/qml/controlsUit/AttachmentsTable.qml similarity index 98% rename from interface/resources/qml/controls-uit/AttachmentsTable.qml rename to interface/resources/qml/controlsUit/AttachmentsTable.qml index 8ee9909ab8..a2677962da 100644 --- a/interface/resources/qml/controls-uit/AttachmentsTable.qml +++ b/interface/resources/qml/controlsUit/AttachmentsTable.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.XmlListModel 2.0 -import "../styles-uit" -import "../controls-uit" as HifiControls +import "../stylesUit" +import "." as HifiControls import "../windows" import "../hifi/models" diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controlsUit/BaseWebView.qml similarity index 100% rename from interface/resources/qml/controls-uit/BaseWebView.qml rename to interface/resources/qml/controlsUit/BaseWebView.qml diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controlsUit/Button.qml similarity index 99% rename from interface/resources/qml/controls-uit/Button.qml rename to interface/resources/qml/controlsUit/Button.qml index f1a6e4bb4a..6ea7ce4b4c 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controlsUit/Button.qml @@ -12,7 +12,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 as Original import TabletScriptingInterface 1.0 -import "../styles-uit" +import "../stylesUit" Original.Button { id: control; diff --git a/interface/resources/qml/controls-uit/CheckBox.qml b/interface/resources/qml/controlsUit/CheckBox.qml similarity index 99% rename from interface/resources/qml/controls-uit/CheckBox.qml rename to interface/resources/qml/controlsUit/CheckBox.qml index 6e4a3df010..abf08908fb 100644 --- a/interface/resources/qml/controls-uit/CheckBox.qml +++ b/interface/resources/qml/controlsUit/CheckBox.qml @@ -11,7 +11,7 @@ import QtQuick 2.2 import QtQuick.Controls 2.2 as Original -import "../styles-uit" +import "../stylesUit" import TabletScriptingInterface 1.0 diff --git a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml b/interface/resources/qml/controlsUit/CheckBoxQQC2.qml similarity index 98% rename from interface/resources/qml/controls-uit/CheckBoxQQC2.qml rename to interface/resources/qml/controlsUit/CheckBoxQQC2.qml index 8a9686ff5e..91d35ecd58 100644 --- a/interface/resources/qml/controls-uit/CheckBoxQQC2.qml +++ b/interface/resources/qml/controlsUit/CheckBoxQQC2.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../styles-uit" -import "../controls-uit" as HiFiControls +import "../stylesUit" +import "." as HiFiControls import TabletScriptingInterface 1.0 CheckBox { diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controlsUit/ComboBox.qml similarity index 99% rename from interface/resources/qml/controls-uit/ComboBox.qml rename to interface/resources/qml/controlsUit/ComboBox.qml index 245b565a62..8d1d7a5262 100644 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controlsUit/ComboBox.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../styles-uit" -import "../controls-uit" as HifiControls +import "../stylesUit" +import "." as HifiControls FocusScope { id: root diff --git a/interface/resources/qml/controls-uit/ContentSection.qml b/interface/resources/qml/controlsUit/ContentSection.qml similarity index 99% rename from interface/resources/qml/controls-uit/ContentSection.qml rename to interface/resources/qml/controlsUit/ContentSection.qml index 47a13e9262..262c29220f 100644 --- a/interface/resources/qml/controls-uit/ContentSection.qml +++ b/interface/resources/qml/controlsUit/ContentSection.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../styles-uit" +import "../stylesUit" Column { property string name: "Content Section" diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controlsUit/FilterBar.qml similarity index 99% rename from interface/resources/qml/controls-uit/FilterBar.qml rename to interface/resources/qml/controlsUit/FilterBar.qml index ecae790b22..3e407040bc 100644 --- a/interface/resources/qml/controls-uit/FilterBar.qml +++ b/interface/resources/qml/controlsUit/FilterBar.qml @@ -12,8 +12,8 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import "../styles-uit" -import "../controls-uit" as HifiControls +import "../stylesUit" +import "." as HifiControls Item { id: root; diff --git a/interface/resources/qml/controls-uit/GlyphButton.qml b/interface/resources/qml/controlsUit/GlyphButton.qml similarity index 99% rename from interface/resources/qml/controls-uit/GlyphButton.qml rename to interface/resources/qml/controlsUit/GlyphButton.qml index 9129486720..17f7fba2d6 100644 --- a/interface/resources/qml/controls-uit/GlyphButton.qml +++ b/interface/resources/qml/controlsUit/GlyphButton.qml @@ -12,7 +12,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 as Original import TabletScriptingInterface 1.0 -import "../styles-uit" +import "../stylesUit" Original.Button { id: control diff --git a/interface/resources/qml/controls-uit/HorizontalRule.qml b/interface/resources/qml/controlsUit/HorizontalRule.qml similarity index 100% rename from interface/resources/qml/controls-uit/HorizontalRule.qml rename to interface/resources/qml/controlsUit/HorizontalRule.qml diff --git a/interface/resources/qml/controls-uit/HorizontalSpacer.qml b/interface/resources/qml/controlsUit/HorizontalSpacer.qml similarity index 94% rename from interface/resources/qml/controls-uit/HorizontalSpacer.qml rename to interface/resources/qml/controlsUit/HorizontalSpacer.qml index 545154ab44..efcabf2699 100644 --- a/interface/resources/qml/controls-uit/HorizontalSpacer.qml +++ b/interface/resources/qml/controlsUit/HorizontalSpacer.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../styles-uit" +import "../stylesUit" Item { id: root diff --git a/interface/resources/qml/controls-uit/ImageMessageBox.qml b/interface/resources/qml/controlsUit/ImageMessageBox.qml similarity index 98% rename from interface/resources/qml/controls-uit/ImageMessageBox.qml rename to interface/resources/qml/controlsUit/ImageMessageBox.qml index 74313f7ffe..46d93383a4 100644 --- a/interface/resources/qml/controls-uit/ImageMessageBox.qml +++ b/interface/resources/qml/controlsUit/ImageMessageBox.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "../styles-uit" +import "../stylesUit" Item { id: imageBox diff --git a/interface/resources/qml/controls-uit/Key.qml b/interface/resources/qml/controlsUit/Key.qml similarity index 100% rename from interface/resources/qml/controls-uit/Key.qml rename to interface/resources/qml/controlsUit/Key.qml diff --git a/interface/resources/qml/controls-uit/Keyboard.qml b/interface/resources/qml/controlsUit/Keyboard.qml similarity index 100% rename from interface/resources/qml/controls-uit/Keyboard.qml rename to interface/resources/qml/controlsUit/Keyboard.qml diff --git a/interface/resources/qml/controls-uit/Label.qml b/interface/resources/qml/controlsUit/Label.qml similarity index 97% rename from interface/resources/qml/controls-uit/Label.qml rename to interface/resources/qml/controlsUit/Label.qml index 4c7051b495..7f208cde88 100644 --- a/interface/resources/qml/controls-uit/Label.qml +++ b/interface/resources/qml/controlsUit/Label.qml @@ -10,7 +10,7 @@ import QtQuick 2.7 -import "../styles-uit" +import "../stylesUit" RalewaySemiBold { HifiConstants { id: hifi } diff --git a/interface/resources/qml/controls-uit/QueuedButton.qml b/interface/resources/qml/controlsUit/QueuedButton.qml similarity index 98% rename from interface/resources/qml/controls-uit/QueuedButton.qml rename to interface/resources/qml/controlsUit/QueuedButton.qml index 6612d582df..70ad9eb112 100644 --- a/interface/resources/qml/controls-uit/QueuedButton.qml +++ b/interface/resources/qml/controlsUit/QueuedButton.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../styles-uit" +import "../stylesUit" import "." as HifiControls HifiControls.Button { diff --git a/interface/resources/qml/controls-uit/RadioButton.qml b/interface/resources/qml/controlsUit/RadioButton.qml similarity index 97% rename from interface/resources/qml/controls-uit/RadioButton.qml rename to interface/resources/qml/controlsUit/RadioButton.qml index 56324c55d7..ad62a77aa7 100644 --- a/interface/resources/qml/controls-uit/RadioButton.qml +++ b/interface/resources/qml/controlsUit/RadioButton.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 as Original -import "../styles-uit" -import "../controls-uit" as HifiControls +import "../stylesUit" +import "." as HifiControls import TabletScriptingInterface 1.0 diff --git a/interface/resources/qml/controls-uit/ScrollBar.qml b/interface/resources/qml/controlsUit/ScrollBar.qml similarity index 98% rename from interface/resources/qml/controls-uit/ScrollBar.qml rename to interface/resources/qml/controlsUit/ScrollBar.qml index 125e84e585..bcb1f62429 100644 --- a/interface/resources/qml/controls-uit/ScrollBar.qml +++ b/interface/resources/qml/controlsUit/ScrollBar.qml @@ -11,7 +11,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../styles-uit" +import "../stylesUit" ScrollBar { visible: size < 1.0 diff --git a/interface/resources/qml/controls-uit/Separator.qml b/interface/resources/qml/controlsUit/Separator.qml similarity index 98% rename from interface/resources/qml/controls-uit/Separator.qml rename to interface/resources/qml/controlsUit/Separator.qml index 3350764ae9..da6b9adf57 100644 --- a/interface/resources/qml/controls-uit/Separator.qml +++ b/interface/resources/qml/controlsUit/Separator.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "../styles-uit" +import "../stylesUit" Item { property int colorScheme: 0; diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controlsUit/Slider.qml similarity index 98% rename from interface/resources/qml/controls-uit/Slider.qml rename to interface/resources/qml/controlsUit/Slider.qml index 2a5d4c137d..8cb08b69e2 100644 --- a/interface/resources/qml/controls-uit/Slider.qml +++ b/interface/resources/qml/controlsUit/Slider.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../styles-uit" -import "../controls-uit" as HifiControls +import "../stylesUit" +import "." as HifiControls Slider { id: slider diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controlsUit/SpinBox.qml similarity index 98% rename from interface/resources/qml/controls-uit/SpinBox.qml rename to interface/resources/qml/controlsUit/SpinBox.qml index 3d3ea7a75e..d24c7c5e8c 100644 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controlsUit/SpinBox.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../styles-uit" -import "../controls-uit" as HifiControls +import "../stylesUit" +import "." as HifiControls SpinBox { id: spinBox diff --git a/interface/resources/qml/controls-uit/Switch.qml b/interface/resources/qml/controlsUit/Switch.qml similarity index 99% rename from interface/resources/qml/controls-uit/Switch.qml rename to interface/resources/qml/controlsUit/Switch.qml index bfe86b1420..0961ef2500 100644 --- a/interface/resources/qml/controls-uit/Switch.qml +++ b/interface/resources/qml/controlsUit/Switch.qml @@ -11,7 +11,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 as Original -import "../styles-uit" +import "../stylesUit" Item { id: rootSwitch; diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controlsUit/Table.qml similarity index 99% rename from interface/resources/qml/controls-uit/Table.qml rename to interface/resources/qml/controlsUit/Table.qml index ce4e1c376a..ab74361046 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controlsUit/Table.qml @@ -13,7 +13,7 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Controls 2.3 as QQC2 -import "../styles-uit" +import "../stylesUit" TableView { id: tableView diff --git a/interface/resources/qml/controls-uit/TabletContentSection.qml b/interface/resources/qml/controlsUit/TabletContentSection.qml similarity index 99% rename from interface/resources/qml/controls-uit/TabletContentSection.qml rename to interface/resources/qml/controlsUit/TabletContentSection.qml index c34f4afdd6..dccaf31bbe 100644 --- a/interface/resources/qml/controls-uit/TabletContentSection.qml +++ b/interface/resources/qml/controlsUit/TabletContentSection.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../styles-uit" +import "../stylesUit" Column { property string name: "Content Section" diff --git a/interface/resources/qml/controls-uit/TabletHeader.qml b/interface/resources/qml/controlsUit/TabletHeader.qml similarity index 96% rename from interface/resources/qml/controls-uit/TabletHeader.qml rename to interface/resources/qml/controlsUit/TabletHeader.qml index 56203de286..f626700742 100644 --- a/interface/resources/qml/controls-uit/TabletHeader.qml +++ b/interface/resources/qml/controlsUit/TabletHeader.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../styles-uit" +import "../stylesUit" Rectangle { diff --git a/interface/resources/qml/controls-uit/TextAction.qml b/interface/resources/qml/controlsUit/TextAction.qml similarity index 96% rename from interface/resources/qml/controls-uit/TextAction.qml rename to interface/resources/qml/controlsUit/TextAction.qml index 1745a6c273..a0a1bb7d07 100644 --- a/interface/resources/qml/controls-uit/TextAction.qml +++ b/interface/resources/qml/controlsUit/TextAction.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../styles-uit" -import "../controls-uit" as HifiControls +import "../stylesUit" +import "." as HifiControls Item { property string icon: "" diff --git a/interface/resources/qml/controls-uit/TextEdit.qml b/interface/resources/qml/controlsUit/TextEdit.qml similarity index 95% rename from interface/resources/qml/controls-uit/TextEdit.qml rename to interface/resources/qml/controlsUit/TextEdit.qml index a72a3b13d8..7446c5040f 100644 --- a/interface/resources/qml/controls-uit/TextEdit.qml +++ b/interface/resources/qml/controlsUit/TextEdit.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "../styles-uit" +import "../stylesUit" TextEdit { diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controlsUit/TextField.qml similarity index 99% rename from interface/resources/qml/controls-uit/TextField.qml rename to interface/resources/qml/controlsUit/TextField.qml index 917068ac01..d78f3a1340 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controlsUit/TextField.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 -import "../styles-uit" -import "../controls-uit" as HifiControls +import "../stylesUit" +import "." as HifiControls TextField { id: textField diff --git a/interface/resources/qml/controls-uit/ToolTip.qml b/interface/resources/qml/controlsUit/ToolTip.qml similarity index 100% rename from interface/resources/qml/controls-uit/ToolTip.qml rename to interface/resources/qml/controlsUit/ToolTip.qml diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controlsUit/Tree.qml similarity index 99% rename from interface/resources/qml/controls-uit/Tree.qml rename to interface/resources/qml/controlsUit/Tree.qml index 5199a10a27..f2c49095b1 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controlsUit/Tree.qml @@ -15,7 +15,7 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Controls 2.2 as QQC2 -import "../styles-uit" +import "../stylesUit" TreeView { id: treeView diff --git a/interface/resources/qml/controls-uit/VerticalSpacer.qml b/interface/resources/qml/controlsUit/VerticalSpacer.qml similarity index 94% rename from interface/resources/qml/controls-uit/VerticalSpacer.qml rename to interface/resources/qml/controlsUit/VerticalSpacer.qml index 2df65f1002..4c93aa1801 100644 --- a/interface/resources/qml/controls-uit/VerticalSpacer.qml +++ b/interface/resources/qml/controlsUit/VerticalSpacer.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../styles-uit" +import "../stylesUit" Item { id: root diff --git a/interface/resources/qml/controls-uit/WebGlyphButton.qml b/interface/resources/qml/controlsUit/WebGlyphButton.qml similarity index 98% rename from interface/resources/qml/controls-uit/WebGlyphButton.qml rename to interface/resources/qml/controlsUit/WebGlyphButton.qml index fd7cd001b2..7739ecd5e7 100644 --- a/interface/resources/qml/controls-uit/WebGlyphButton.qml +++ b/interface/resources/qml/controlsUit/WebGlyphButton.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 as Original -import "../styles-uit" +import "../stylesUit" Original.Button { id: control diff --git a/interface/resources/qml/controls-uit/WebSpinner.qml b/interface/resources/qml/controlsUit/WebSpinner.qml similarity index 100% rename from interface/resources/qml/controls-uit/WebSpinner.qml rename to interface/resources/qml/controlsUit/WebSpinner.qml diff --git a/interface/resources/qml/controls-uit/WebView.qml b/interface/resources/qml/controlsUit/WebView.qml similarity index 100% rename from interface/resources/qml/controls-uit/WebView.qml rename to interface/resources/qml/controlsUit/WebView.qml diff --git a/interface/resources/qml/controls-uit/qmldir b/interface/resources/qml/controlsUit/qmldir similarity index 100% rename from interface/resources/qml/controls-uit/qmldir rename to interface/resources/qml/controlsUit/qmldir diff --git a/interface/resources/qml/dialogs/AssetDialog.qml b/interface/resources/qml/dialogs/AssetDialog.qml index e8d28e9b37..b8eaab0b8d 100644 --- a/interface/resources/qml/dialogs/AssetDialog.qml +++ b/interface/resources/qml/dialogs/AssetDialog.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import Qt.labs.settings 1.0 -import "../styles-uit" +import stylesUit 1.0 import "../windows" import "assetDialog" diff --git a/interface/resources/qml/dialogs/CustomQueryDialog.qml b/interface/resources/qml/dialogs/CustomQueryDialog.qml index 0c86b93c4b..026068eee1 100644 --- a/interface/resources/qml/dialogs/CustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/CustomQueryDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.7; import QtQuick.Dialogs 1.2 as OriginalDialogs; import QtQuick.Controls 2.3 -import "../controls-uit"; -import "../styles-uit"; +import controlsUit 1.0 +import stylesUit 1.0 import "../windows"; ModalWindow { diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 6651af0db3..b7340575dd 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -16,8 +16,8 @@ import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "fileDialog" diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index b5ac6cab72..9428e3ab6e 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 as OriginalDialogs -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "messageDialog" diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index fffd0e2ed9..9df1d0b963 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../controls-uit" as HifiControls -import "../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 import "../windows" import "preferences" diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index 41ee30e6d5..9cfb3011bd 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" ModalWindow { diff --git a/interface/resources/qml/dialogs/TabletAssetDialog.qml b/interface/resources/qml/dialogs/TabletAssetDialog.qml index 897378e40c..b3bd45f972 100644 --- a/interface/resources/qml/dialogs/TabletAssetDialog.qml +++ b/interface/resources/qml/dialogs/TabletAssetDialog.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../styles-uit" +import stylesUit 1.0 import "../windows" import "assetDialog" diff --git a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml index 81a2c5c1e0..c7772984ab 100644 --- a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 2.3 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" TabletModalWindow { diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 6848c230e3..3be6e30dd0 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -16,8 +16,8 @@ import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "fileDialog" diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index c85b2b2ba0..6314921286 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -11,8 +11,8 @@ import Hifi 1.0 import QtQuick 2.5 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "../LoginDialog" diff --git a/interface/resources/qml/dialogs/TabletMessageBox.qml b/interface/resources/qml/dialogs/TabletMessageBox.qml index fabe0dd247..1e6f0734ad 100644 --- a/interface/resources/qml/dialogs/TabletMessageBox.qml +++ b/interface/resources/qml/dialogs/TabletMessageBox.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 as OriginalDialogs -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" import "messageDialog" diff --git a/interface/resources/qml/dialogs/TabletQueryDialog.qml b/interface/resources/qml/dialogs/TabletQueryDialog.qml index 5746a3d67c..8f63730b8e 100644 --- a/interface/resources/qml/dialogs/TabletQueryDialog.qml +++ b/interface/resources/qml/dialogs/TabletQueryDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 2.3 -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../windows" TabletModalWindow { diff --git a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml index c3e842bc2f..da976ef3e1 100644 --- a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml +++ b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 import QtQuick.Controls 1.5 as QQC1 -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../fileDialog" diff --git a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml index 50a10974b5..6c042b5598 100644 --- a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml +++ b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../../controls-uit" +import controlsUit 1.0 ComboBox { id: root diff --git a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml index 8411980db7..f5715fa2c2 100644 --- a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml +++ b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 -import "../../controls-uit" +import controlsUit 1.0 Button { property var dialog; diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml index 0efc3776b3..9505e70530 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../../controls-uit" +import controlsUit 1.0 import "../../hifi/tablet/tabletWindows/preferences" Preference { diff --git a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml index 2cf50891c9..6059f8ff1c 100644 --- a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml index 454a9124ae..09c5b4329d 100644 --- a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import TabletScriptingInterface 1.0 -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index e2172d8eda..f6f840bbe8 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import TabletScriptingInterface 1.0 -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml index 3b3efaf520..98cb397976 100644 --- a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../controls-uit" as HiFiControls -import "../../styles-uit" +import controlsUit 1.0 as HiFiControls +import stylesUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/EditablePreference.qml b/interface/resources/qml/dialogs/preferences/EditablePreference.qml index 8acf8e1f76..e0c79ebba0 100644 --- a/interface/resources/qml/dialogs/preferences/EditablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/EditablePreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml b/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml index cfc2e94ed9..f963003c59 100644 --- a/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml +++ b/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml index 103904a666..0a09d8d609 100644 --- a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml +++ b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index c2c6583b7e..a9b755ad83 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import Hifi 1.0 -import "../../controls-uit" as HiFiControls -import "../../styles-uit" +import controlsUit 1.0 as HiFiControls +import stylesUit 1.0 import "." Preference { diff --git a/interface/resources/qml/dialogs/preferences/SliderPreference.qml b/interface/resources/qml/dialogs/preferences/SliderPreference.qml index 2bdda09fc3..c8a2aae158 100644 --- a/interface/resources/qml/dialogs/preferences/SliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SliderPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml index b2c334b674..1b080c2759 100644 --- a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 126e62fc30..cbc804d9d7 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import "../../controls-uit" +import controlsUit 1.0 Preference { id: root diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android/ActionBar.qml index d487901d6f..3c58156f30 100644 --- a/interface/resources/qml/hifi/+android/ActionBar.qml +++ b/interface/resources/qml/hifi/+android/ActionBar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android/AudioBar.qml index 6cc17fccf7..912572fdf8 100644 --- a/interface/resources/qml/hifi/+android/AudioBar.qml +++ b/interface/resources/qml/hifi/+android/AudioBar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/AvatarOption.qml b/interface/resources/qml/hifi/+android/AvatarOption.qml index 85d7e52eb2..7eba3c2a67 100644 --- a/interface/resources/qml/hifi/+android/AvatarOption.qml +++ b/interface/resources/qml/hifi/+android/AvatarOption.qml @@ -11,7 +11,7 @@ import QtQuick.Layouts 1.3 import QtQuick 2.5 -import "../controls-uit" as HifiControlsUit +import controlsUit 1.0 as HifiControlsUit ColumnLayout { id: itemRoot diff --git a/interface/resources/qml/hifi/+android/StatsBar.qml b/interface/resources/qml/hifi/+android/StatsBar.qml index aee438b44f..64e93b4a08 100644 --- a/interface/resources/qml/hifi/+android/StatsBar.qml +++ b/interface/resources/qml/hifi/+android/StatsBar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/WindowHeader.qml b/interface/resources/qml/hifi/+android/WindowHeader.qml index 4ec0a0c6e6..5316fc4786 100644 --- a/interface/resources/qml/hifi/+android/WindowHeader.qml +++ b/interface/resources/qml/hifi/+android/WindowHeader.qml @@ -16,8 +16,8 @@ import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 import "." import "../styles" as HifiStyles -import "../styles-uit" -import "../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/bottomHudOptions.qml b/interface/resources/qml/hifi/+android/bottomHudOptions.qml index 22beccf531..6b830d94c2 100644 --- a/interface/resources/qml/hifi/+android/bottomHudOptions.qml +++ b/interface/resources/qml/hifi/+android/bottomHudOptions.qml @@ -16,8 +16,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 import "../../styles" as HifiStyles -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." import "." diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index 994bf1efe4..1bf04fb8d9 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 1a7f5bac40..ad337a6361 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -14,8 +14,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import "../styles-uit" -import "../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../windows" as Windows import "../dialogs" diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 83bf1e2c54..7f29324416 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -17,7 +17,7 @@ import QtGraphicalEffects 1.0 import TabletScriptingInterface 1.0 import "toolbars" -import "../styles-uit" +import stylesUit 1.0 Item { id: root; diff --git a/interface/resources/qml/hifi/ComboDialog.qml b/interface/resources/qml/hifi/ComboDialog.qml index e5dc8a9c1a..74d9c1019b 100644 --- a/interface/resources/qml/hifi/ComboDialog.qml +++ b/interface/resources/qml/hifi/ComboDialog.qml @@ -10,8 +10,8 @@ // import QtQuick 2.5 -import "../styles-uit" -import "../controls-uit" +import stylesUit 1.0 +import controlsUit 1.0 Item { property var dialogTitleText : ""; diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 4d342fe775..511d9377e5 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -8,7 +8,7 @@ import "../desktop" as OriginalDesktop import ".." import "." import "./toolbars" -import "../controls-uit" +import controlsUit 1.0 OriginalDesktop.Desktop { id: desktop diff --git a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml index 9e9dcc75b2..048add24e5 100644 --- a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml +++ b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml @@ -10,7 +10,7 @@ // import QtQuick 2.5 -import "../styles-uit" +import stylesUit 1.0 Item { property alias text: popupText.text diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 346481fe1f..4cfd4804b3 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -15,7 +15,7 @@ import Hifi 1.0 import QtQuick 2.5 import QtGraphicalEffects 1.0 import "toolbars" -import "../styles-uit" +import stylesUit 1.0 import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. Column { diff --git a/interface/resources/qml/hifi/LetterboxMessage.qml b/interface/resources/qml/hifi/LetterboxMessage.qml index 8a18d88842..68bebdd041 100644 --- a/interface/resources/qml/hifi/LetterboxMessage.qml +++ b/interface/resources/qml/hifi/LetterboxMessage.qml @@ -10,7 +10,7 @@ // import QtQuick 2.5 -import "../styles-uit" +import stylesUit 1.0 Item { property alias text: popupText.text diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index dfa6555150..242ca5ab57 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -13,8 +13,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 -import "../styles-uit" -import "../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "toolbars" // references Users, UserActivityLogger, MyAvatar, Vec3, Quat, AddressManager, Account from root context diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 1384cb8711..368beaab47 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -15,8 +15,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 import Qt.labs.settings 1.0 -import "../styles-uit" -import "../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../controls" as HifiControls import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. diff --git a/interface/resources/qml/hifi/SkyboxChanger.qml b/interface/resources/qml/hifi/SkyboxChanger.qml index f0c97a11a3..a66fc38415 100644 --- a/interface/resources/qml/hifi/SkyboxChanger.qml +++ b/interface/resources/qml/hifi/SkyboxChanger.qml @@ -10,8 +10,8 @@ // import QtQuick 2.5 -import "../styles-uit" -import "../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import QtQuick.Controls 2.2 Item { diff --git a/interface/resources/qml/hifi/SpectatorCamera.qml b/interface/resources/qml/hifi/SpectatorCamera.qml index 4bf80e410b..09b722b906 100644 --- a/interface/resources/qml/hifi/SpectatorCamera.qml +++ b/interface/resources/qml/hifi/SpectatorCamera.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 -import "../styles-uit" -import "../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../controls" as HifiControls // references HMD, XXX from root context diff --git a/interface/resources/qml/hifi/TabletTextButton.qml b/interface/resources/qml/hifi/TabletTextButton.qml index e5ff1d381d..6c9e0331df 100644 --- a/interface/resources/qml/hifi/TabletTextButton.qml +++ b/interface/resources/qml/hifi/TabletTextButton.qml @@ -10,7 +10,7 @@ import Hifi 1.0 import QtQuick 2.4 -import "../styles-uit" +import stylesUit 1.0 Rectangle { property alias text: label.text diff --git a/interface/resources/qml/hifi/TextButton.qml b/interface/resources/qml/hifi/TextButton.qml index 02e49d86e4..61588a9603 100644 --- a/interface/resources/qml/hifi/TextButton.qml +++ b/interface/resources/qml/hifi/TextButton.qml @@ -9,7 +9,7 @@ // import Hifi 1.0 import QtQuick 2.4 -import "../styles-uit" +import stylesUit 1.0 Rectangle { property alias text: label.text; diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index ab93752d92..c05de26471 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -18,8 +18,8 @@ import QtGraphicalEffects 1.0 import QtWebEngine 1.5 import QtWebChannel 1.0 -import "../styles-uit" -import "../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../windows" import "../controls" diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index f4a708567a..c8dd83cd62 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -15,8 +15,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import "./" as AudioControls diff --git a/interface/resources/qml/hifi/audio/AudioTabButton.qml b/interface/resources/qml/hifi/audio/AudioTabButton.qml index 3a3ed90f5e..32331ccb6e 100644 --- a/interface/resources/qml/hifi/audio/AudioTabButton.qml +++ b/interface/resources/qml/hifi/audio/AudioTabButton.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 TabButton { id: control diff --git a/interface/resources/qml/hifi/audio/CheckBox.qml b/interface/resources/qml/hifi/audio/CheckBox.qml index 3a954d4004..5ab62a5091 100644 --- a/interface/resources/qml/hifi/audio/CheckBox.qml +++ b/interface/resources/qml/hifi/audio/CheckBox.qml @@ -11,7 +11,7 @@ import QtQuick 2.7 -import "../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls HifiControls.CheckBoxQQC2 { color: "white" diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index 2b9599a3cc..cfe55af9c4 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -13,8 +13,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls RowLayout { property var sound: null; diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index b13f23f17d..ac6aa3d56c 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Controls 1.4 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet import "../common" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml index 9d9216c461..8cfea0bcd9 100644 --- a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml +++ b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 1b77dcd3e9..429f993817 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml index 5f874d3f04..6002747596 100644 --- a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml +++ b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml index 41eacd68d5..1eb8af31e6 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml @@ -16,8 +16,8 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 -import "../../../../styles-uit" -import "../../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../../controls" as HifiControls import "../../wallet" as HifiWallet diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml index 9293dc83ab..9e1a967d50 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.6 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import "../../../../styles-uit" -import "../../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index a515c8031f..3614e68426 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.6 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import "../../../../styles-uit" -import "../../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index d24344b40a..7721dc3142 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index eeb9ac3c54..9d2df1a865 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet import TabletScriptingInterface 1.0 diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 2435678e77..778b09e86d 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. import "../wallet" as HifiWallet diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 6d8fc3c33f..24ca5407b2 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml index eadf1ca8a2..b1fbb91c80 100644 --- a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml +++ b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml index 8451c90836..6ddfe0da1c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index c4abd40d2a..86d50e87ec 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index e052b78876..179ffcf707 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 14ac696ef7..f0b1ecd4e0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml index 01df18352b..da0d0d59d5 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml index 599c6a1851..82933eebcb 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 588b80c435..d0d2146edd 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon import "../common/sendAsset" diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml b/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml index 19065ee542..e7163a3641 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import "../common" as HifiCommerceCommon -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit Item { diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 627da1d43f..1e78027f91 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 import QtQuick.Controls 2.2 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index dc6ce45a74..b793075843 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/dialogs/AboutDialog.qml b/interface/resources/qml/hifi/dialogs/AboutDialog.qml index b8e6e89aec..3d5d1a94a3 100644 --- a/interface/resources/qml/hifi/dialogs/AboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AboutDialog.qml @@ -10,7 +10,7 @@ import QtQuick 2.8 -import "../../styles-uit" +import stylesUit 1.0 import "../../windows" ScrollingWindow { diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 9a180a66f6..be17e65ab3 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import "../" diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index 579aa1cb1e..d26bf81e57 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "../../styles-uit" +import stylesUit 1.0 Rectangle { width: 480 diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 0eeb252049..f665032b01 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -14,8 +14,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import ".." diff --git a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml index afe06897df..763f56b92b 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" Rectangle { diff --git a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml index 50df4dedbc..213dca8b48 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import Hifi 1.0 as Hifi -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Rectangle { id: root diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml index 24798af21a..4cfc99e0eb 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" Rectangle { diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml index d5c5a5ee02..e86dfd7554 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Column { id: root diff --git a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml index ab53f03477..bb3d668850 100644 --- a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml +++ b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" Rectangle { diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index 018c8f5737..6cd220307d 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import "../" diff --git a/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml b/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml index ce1abc6154..b1aa8e5c45 100644 --- a/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml +++ b/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml @@ -1,7 +1,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 -import "../../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls Column { width: pane.contentWidth diff --git a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml index e3115a5738..6b2aa331e8 100644 --- a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml +++ b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml @@ -10,9 +10,9 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../styles-uit" +import stylesUit 1.0 import "../../controls" -import "../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 6706830537..b8bbd71f33 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -11,9 +11,9 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import Qt.labs.settings 1.0 -import "../../styles-uit" +import stylesUit 1.0 import "../../controls" -import "../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls import "../../dialogs" import "../../dialogs/preferences" import "tabletWindows" diff --git a/interface/resources/qml/hifi/tablet/EditTabButton.qml b/interface/resources/qml/hifi/tablet/EditTabButton.qml index 13894f4d15..5fc4341eb8 100644 --- a/interface/resources/qml/hifi/tablet/EditTabButton.qml +++ b/interface/resources/qml/hifi/tablet/EditTabButton.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 TabButton { id: control diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index 4ac8755570..332fab6112 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -4,8 +4,8 @@ import QtWebChannel 1.0 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 TabBar { id: editTabView diff --git a/interface/resources/qml/hifi/tablet/InputRecorder.qml b/interface/resources/qml/hifi/tablet/InputRecorder.qml index 527a6cacb4..9b63a612a8 100644 --- a/interface/resources/qml/hifi/tablet/InputRecorder.qml +++ b/interface/resources/qml/hifi/tablet/InputRecorder.qml @@ -9,8 +9,8 @@ import QtQuick 2.5 import Hifi 1.0 -import "../../styles-uit" -import "../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../../windows" import "../../dialogs" diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml index 526a42f8e2..dde372648b 100644 --- a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml @@ -13,8 +13,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Dialogs 1.2 as OriginalDialogs -import "../../styles-uit" -import "../../controls-uit" +import stylesUit 1.0 +import controlsUit 1.0 import "../dialogs" Rectangle { diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 10b844c987..01f1fa71e4 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 as OriginalDialogs -import "../../styles-uit" -import "../../controls-uit" +import stylesUit 1.0 +import controlsUit 1.0 import "../dialogs" Rectangle { diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index c2aff08e35..684d12c9b4 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -9,9 +9,9 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../styles-uit" +import stylesUit 1.0 import "../../controls" -import "../../controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls import "." diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 3d518289fb..0f26ba20aa 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -18,8 +18,8 @@ import "../../styles" import "../../windows" import "../" import "../toolbars" -import "../../styles-uit" as HifiStyles -import "../../controls-uit" as HifiControls +import stylesUit 1.0 as HifiStyles +import controlsUit 1.0 as HifiControls import QtQuick.Controls 2.2 as QQC2 import QtQuick.Templates 2.2 as T diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index 1922b02f93..934ed91995 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -6,7 +6,7 @@ import QtQuick.Layouts 1.3 import TabletScriptingInterface 1.0 import "." -import "../../styles-uit" +import stylesUit 1.0 import "../audio" as HifiAudio Item { diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 6540d53fca..267fb9f0cf 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -7,7 +7,7 @@ import QtWebEngine 1.1 import "." -import "../../styles-uit" +import stylesUit 1.0 import "../../controls" FocusScope { diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml index 74f175e049..25db90c771 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: root diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index b632a17e57..73b0405984 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import TabletScriptingInterface 1.0 -import "../../styles-uit" +import stylesUit 1.0 import "." FocusScope { diff --git a/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml b/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml index d69d760b95..ce4e641476 100644 --- a/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 import "../dialogs/content" Item { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index 871d1c92a9..8e91655dda 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -16,8 +16,8 @@ import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." -import "../../../controls-uit" -import "../../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../../../windows" import "../../../dialogs/fileDialog" diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index 3708f75114..57ca705352 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import "." import "./preferences" -import "../../../styles-uit" -import "../../../controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { id: dialog diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 6ac3f706e4..57fdeb482b 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import Hifi 1.0 import "../../../../dialogs/preferences" -import "../../../../controls-uit" as HiFiControls -import "../../../../styles-uit" +import controlsUit 1.0 as HiFiControls +import stylesUit 1.0 import "." Preference { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml index 8c0e934971..36b927f5f9 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../../../dialogs" -import "../../../../controls-uit" +import controlsUit 1.0 import "../" Preference { diff --git a/interface/resources/qml/styles-uit/+android/HifiConstants.qml b/interface/resources/qml/stylesUit/+android/HifiConstants.qml similarity index 100% rename from interface/resources/qml/styles-uit/+android/HifiConstants.qml rename to interface/resources/qml/stylesUit/+android/HifiConstants.qml diff --git a/interface/resources/qml/styles-uit/AnonymousProRegular.qml b/interface/resources/qml/stylesUit/AnonymousProRegular.qml similarity index 100% rename from interface/resources/qml/styles-uit/AnonymousProRegular.qml rename to interface/resources/qml/stylesUit/AnonymousProRegular.qml diff --git a/interface/resources/qml/styles-uit/ButtonLabel.qml b/interface/resources/qml/stylesUit/ButtonLabel.qml similarity index 100% rename from interface/resources/qml/styles-uit/ButtonLabel.qml rename to interface/resources/qml/stylesUit/ButtonLabel.qml diff --git a/interface/resources/qml/styles-uit/FiraSansRegular.qml b/interface/resources/qml/stylesUit/FiraSansRegular.qml similarity index 100% rename from interface/resources/qml/styles-uit/FiraSansRegular.qml rename to interface/resources/qml/stylesUit/FiraSansRegular.qml diff --git a/interface/resources/qml/styles-uit/FiraSansSemiBold.qml b/interface/resources/qml/stylesUit/FiraSansSemiBold.qml similarity index 100% rename from interface/resources/qml/styles-uit/FiraSansSemiBold.qml rename to interface/resources/qml/stylesUit/FiraSansSemiBold.qml diff --git a/interface/resources/qml/styles-uit/HiFiGlyphs.qml b/interface/resources/qml/stylesUit/HiFiGlyphs.qml similarity index 100% rename from interface/resources/qml/styles-uit/HiFiGlyphs.qml rename to interface/resources/qml/stylesUit/HiFiGlyphs.qml diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/stylesUit/HifiConstants.qml similarity index 100% rename from interface/resources/qml/styles-uit/HifiConstants.qml rename to interface/resources/qml/stylesUit/HifiConstants.qml diff --git a/interface/resources/qml/styles-uit/IconButton.qml b/interface/resources/qml/stylesUit/IconButton.qml similarity index 100% rename from interface/resources/qml/styles-uit/IconButton.qml rename to interface/resources/qml/stylesUit/IconButton.qml diff --git a/interface/resources/qml/styles-uit/InfoItem.qml b/interface/resources/qml/stylesUit/InfoItem.qml similarity index 100% rename from interface/resources/qml/styles-uit/InfoItem.qml rename to interface/resources/qml/stylesUit/InfoItem.qml diff --git a/interface/resources/qml/styles-uit/InputLabel.qml b/interface/resources/qml/stylesUit/InputLabel.qml similarity index 100% rename from interface/resources/qml/styles-uit/InputLabel.qml rename to interface/resources/qml/stylesUit/InputLabel.qml diff --git a/interface/resources/qml/styles-uit/ListItem.qml b/interface/resources/qml/stylesUit/ListItem.qml similarity index 100% rename from interface/resources/qml/styles-uit/ListItem.qml rename to interface/resources/qml/stylesUit/ListItem.qml diff --git a/interface/resources/qml/styles-uit/Logs.qml b/interface/resources/qml/stylesUit/Logs.qml similarity index 100% rename from interface/resources/qml/styles-uit/Logs.qml rename to interface/resources/qml/stylesUit/Logs.qml diff --git a/interface/resources/qml/styles-uit/OverlayTitle.qml b/interface/resources/qml/stylesUit/OverlayTitle.qml similarity index 100% rename from interface/resources/qml/styles-uit/OverlayTitle.qml rename to interface/resources/qml/stylesUit/OverlayTitle.qml diff --git a/interface/resources/qml/styles-uit/RalewayBold.qml b/interface/resources/qml/stylesUit/RalewayBold.qml similarity index 100% rename from interface/resources/qml/styles-uit/RalewayBold.qml rename to interface/resources/qml/stylesUit/RalewayBold.qml diff --git a/interface/resources/qml/styles-uit/RalewayLight.qml b/interface/resources/qml/stylesUit/RalewayLight.qml similarity index 100% rename from interface/resources/qml/styles-uit/RalewayLight.qml rename to interface/resources/qml/stylesUit/RalewayLight.qml diff --git a/interface/resources/qml/styles-uit/RalewayRegular.qml b/interface/resources/qml/stylesUit/RalewayRegular.qml similarity index 100% rename from interface/resources/qml/styles-uit/RalewayRegular.qml rename to interface/resources/qml/stylesUit/RalewayRegular.qml diff --git a/interface/resources/qml/styles-uit/RalewaySemiBold.qml b/interface/resources/qml/stylesUit/RalewaySemiBold.qml similarity index 100% rename from interface/resources/qml/styles-uit/RalewaySemiBold.qml rename to interface/resources/qml/stylesUit/RalewaySemiBold.qml diff --git a/interface/resources/qml/styles-uit/SectionName.qml b/interface/resources/qml/stylesUit/SectionName.qml similarity index 100% rename from interface/resources/qml/styles-uit/SectionName.qml rename to interface/resources/qml/stylesUit/SectionName.qml diff --git a/interface/resources/qml/styles-uit/Separator.qml b/interface/resources/qml/stylesUit/Separator.qml similarity index 97% rename from interface/resources/qml/styles-uit/Separator.qml rename to interface/resources/qml/stylesUit/Separator.qml index 4134b928a7..d9f11e192c 100644 --- a/interface/resources/qml/styles-uit/Separator.qml +++ b/interface/resources/qml/stylesUit/Separator.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "../styles-uit" +import "." Item { // Size diff --git a/interface/resources/qml/styles-uit/ShortcutText.qml b/interface/resources/qml/stylesUit/ShortcutText.qml similarity index 100% rename from interface/resources/qml/styles-uit/ShortcutText.qml rename to interface/resources/qml/stylesUit/ShortcutText.qml diff --git a/interface/resources/qml/styles-uit/TabName.qml b/interface/resources/qml/stylesUit/TabName.qml similarity index 100% rename from interface/resources/qml/styles-uit/TabName.qml rename to interface/resources/qml/stylesUit/TabName.qml diff --git a/interface/resources/qml/styles-uit/TextFieldInput.qml b/interface/resources/qml/stylesUit/TextFieldInput.qml similarity index 100% rename from interface/resources/qml/styles-uit/TextFieldInput.qml rename to interface/resources/qml/stylesUit/TextFieldInput.qml diff --git a/interface/resources/qml/styles-uit/qmldir b/interface/resources/qml/stylesUit/qmldir similarity index 100% rename from interface/resources/qml/styles-uit/qmldir rename to interface/resources/qml/stylesUit/qmldir diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml index f8fd9f4e6c..efaea6be8a 100644 --- a/interface/resources/qml/windows/Decoration.qml +++ b/interface/resources/qml/windows/Decoration.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 Rectangle { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/DefaultFrame.qml b/interface/resources/qml/windows/DefaultFrame.qml index 60e744bec3..5a366e367b 100644 --- a/interface/resources/qml/windows/DefaultFrame.qml +++ b/interface/resources/qml/windows/DefaultFrame.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "." -import "../styles-uit" +import stylesUit 1.0 Frame { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml index 1ddd83976e..fb0dd55985 100644 --- a/interface/resources/qml/windows/DefaultFrameDecoration.qml +++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 Decoration { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/Fadable.qml b/interface/resources/qml/windows/Fadable.qml index 406c6be556..6d88fb067a 100644 --- a/interface/resources/qml/windows/Fadable.qml +++ b/interface/resources/qml/windows/Fadable.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../styles-uit" +import stylesUit 1.0 // Enable window visibility transitions FocusScope { diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 271d4f2e07..7b0fbf8d8c 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../styles-uit" +import stylesUit 1.0 import "../js/Utils.js" as Utils Item { diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index cb23ccd5ad..ae149224e3 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import "." -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Frame { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml index c156b80388..4cab96701e 100644 --- a/interface/resources/qml/windows/ScrollingWindow.qml +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -14,8 +14,8 @@ import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" -import "../controls-uit" as HiFiControls +import stylesUit 1.0 +import controlsUit 1.0 as HiFiControls // FIXME how do I set the initial position of a window without // overriding places where the a individual client of the window diff --git a/interface/resources/qml/windows/TabletModalFrame.qml b/interface/resources/qml/windows/TabletModalFrame.qml index 550eec8357..1e9310eb5a 100644 --- a/interface/resources/qml/windows/TabletModalFrame.qml +++ b/interface/resources/qml/windows/TabletModalFrame.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import "." -import "../controls-uit" -import "../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Rectangle { diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml index 20c86afb5e..bb2bada498 100644 --- a/interface/resources/qml/windows/ToolFrame.qml +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 Frame { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/ToolFrameDecoration.qml b/interface/resources/qml/windows/ToolFrameDecoration.qml index ba36a2a38c..4f149037b3 100644 --- a/interface/resources/qml/windows/ToolFrameDecoration.qml +++ b/interface/resources/qml/windows/ToolFrameDecoration.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 Decoration { id: root diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 835967c628..9f180af55d 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import "../styles-uit" +import stylesUit 1.0 // FIXME how do I set the initial position of a window without // overriding places where the a individual client of the window diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 74098f69c7..f67a356078 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -250,6 +250,7 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { engine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory); auto importList = engine->importPathList(); + importList.insert(importList.begin(), PathUtils::resourcesPath() + "qml/"); importList.insert(importList.begin(), PathUtils::resourcesPath()); engine->setImportPathList(importList); for (const auto& path : importList) { diff --git a/scripts/developer/tests/ControlsGallery.qml b/scripts/developer/tests/ControlsGallery.qml index ceb8a26dc9..9685fa6fe8 100644 --- a/scripts/developer/tests/ControlsGallery.qml +++ b/scripts/developer/tests/ControlsGallery.qml @@ -2,16 +2,9 @@ import QtQuick 2.10 import QtQuick.Window 2.10 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import "qrc:////qml//styles-uit" as HifiStylesUit -import "qrc:////qml//controls-uit" as HifiControlsUit -//uncomment to use from qmlscratch tool -//import '../../../interface/resources/qml/controls-uit' as HifiControlsUit -//import '../../../interface/resources/qml/styles-uit' - -//uncomment to use with HIFI_USE_SOURCE_TREE_RESOURCES=1 -//import '../../../resources/qml/controls-uit' as HifiControlsUit -//import '../../../resources/qml/styles-uit' +import stylesUit 1.0 as HifiStylesUit +import controlsUit 1.0 as HifiControlsUit Item { visible: true From d4286636c86f6f1ba46df8e35c149fb3fd9425c8 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Sun, 17 Jun 2018 14:53:52 +0300 Subject: [PATCH 135/276] repurpose qmlscratch into 'controls gallery' launcher --- tests-manual/ui/qml/ControlsGalleryWindow.qml | 14 + tests-manual/ui/qml/Palettes.qml | 150 ---- tests-manual/ui/qml/ScrollingGraph.qml | 111 --- tests-manual/ui/qml/StubMenu.qml | 730 ------------------ tests-manual/ui/qml/Stubs.qml | 346 --------- tests-manual/ui/qml/TestControllers.qml | 160 ---- tests-manual/ui/qml/TestDialog.qml | 94 --- tests-manual/ui/qml/TestMenu.qml | 10 - tests-manual/ui/qml/TestRoot.qml | 43 -- .../ui/qml/controlDemo/ButtonPage.qml | 128 --- tests-manual/ui/qml/controlDemo/InputPage.qml | 114 --- .../ui/qml/controlDemo/ProgressPage.qml | 90 --- tests-manual/ui/qml/controlDemo/main.qml | 161 ---- tests-manual/ui/qml/main.qml | 461 ----------- tests-manual/ui/qmlscratch.pro | 28 +- tests-manual/ui/src/main.cpp | 125 +-- 16 files changed, 40 insertions(+), 2725 deletions(-) create mode 100644 tests-manual/ui/qml/ControlsGalleryWindow.qml delete mode 100644 tests-manual/ui/qml/Palettes.qml delete mode 100644 tests-manual/ui/qml/ScrollingGraph.qml delete mode 100644 tests-manual/ui/qml/StubMenu.qml delete mode 100644 tests-manual/ui/qml/Stubs.qml delete mode 100644 tests-manual/ui/qml/TestControllers.qml delete mode 100644 tests-manual/ui/qml/TestDialog.qml delete mode 100644 tests-manual/ui/qml/TestMenu.qml delete mode 100644 tests-manual/ui/qml/TestRoot.qml delete mode 100644 tests-manual/ui/qml/controlDemo/ButtonPage.qml delete mode 100644 tests-manual/ui/qml/controlDemo/InputPage.qml delete mode 100644 tests-manual/ui/qml/controlDemo/ProgressPage.qml delete mode 100644 tests-manual/ui/qml/controlDemo/main.qml delete mode 100644 tests-manual/ui/qml/main.qml diff --git a/tests-manual/ui/qml/ControlsGalleryWindow.qml b/tests-manual/ui/qml/ControlsGalleryWindow.qml new file mode 100644 index 0000000000..32fd62da36 --- /dev/null +++ b/tests-manual/ui/qml/ControlsGalleryWindow.qml @@ -0,0 +1,14 @@ +import QtQuick 2.0 +import QtQuick.Window 2.3 +import QtQuick.Controls 1.4 +import '../../../scripts/developer/tests' as Tests + +ApplicationWindow { + width: 640 + height: 480 + visible: true + + Tests.ControlsGallery { + anchors.fill: parent + } +} diff --git a/tests-manual/ui/qml/Palettes.qml b/tests-manual/ui/qml/Palettes.qml deleted file mode 100644 index 2bdf6eba8b..0000000000 --- a/tests-manual/ui/qml/Palettes.qml +++ /dev/null @@ -1,150 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 - -Rectangle { - color: "teal" - height: 512 - width: 192 - SystemPalette { id: sp; colorGroup: SystemPalette.Active } - SystemPalette { id: spi; colorGroup: SystemPalette.Inactive } - SystemPalette { id: spd; colorGroup: SystemPalette.Disabled } - - Column { - anchors.margins: 8 - anchors.fill: parent - spacing: 8 - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "base" } - Rectangle { height: parent.height; width: 16; color: sp.base } - Rectangle { height: parent.height; width: 16; color: spi.base } - Rectangle { height: parent.height; width: 16; color: spd.base } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "alternateBase" } - Rectangle { height: parent.height; width: 16; color: sp.alternateBase } - Rectangle { height: parent.height; width: 16; color: spi.alternateBase } - Rectangle { height: parent.height; width: 16; color: spd.alternateBase } - } - Item { - height: 16 - width:parent.width - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "dark" } - Rectangle { height: parent.height; width: 16; color: sp.dark } - Rectangle { height: parent.height; width: 16; color: spi.dark } - Rectangle { height: parent.height; width: 16; color: spd.dark } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "mid" } - Rectangle { height: parent.height; width: 16; color: sp.mid } - Rectangle { height: parent.height; width: 16; color: spi.mid } - Rectangle { height: parent.height; width: 16; color: spd.mid } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "mid light" } - Rectangle { height: parent.height; width: 16; color: sp.midlight } - Rectangle { height: parent.height; width: 16; color: spi.midlight } - Rectangle { height: parent.height; width: 16; color: spd.midlight } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "light" } - Rectangle { height: parent.height; width: 16; color: sp.light} - Rectangle { height: parent.height; width: 16; color: spi.light} - Rectangle { height: parent.height; width: 16; color: spd.light} - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "shadow" } - Rectangle { height: parent.height; width: 16; color: sp.shadow} - Rectangle { height: parent.height; width: 16; color: spi.shadow} - Rectangle { height: parent.height; width: 16; color: spd.shadow} - } - Item { - height: 16 - width:parent.width - } - - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "text" } - Rectangle { height: parent.height; width: 16; color: sp.text } - Rectangle { height: parent.height; width: 16; color: spi.text } - Rectangle { height: parent.height; width: 16; color: spd.text } - } - Item { - height: 16 - width:parent.width - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "window" } - Rectangle { height: parent.height; width: 16; color: sp.window } - Rectangle { height: parent.height; width: 16; color: spi.window } - Rectangle { height: parent.height; width: 16; color: spd.window } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "window text" } - Rectangle { height: parent.height; width: 16; color: sp.windowText } - Rectangle { height: parent.height; width: 16; color: spi.windowText } - Rectangle { height: parent.height; width: 16; color: spd.windowText } - } - Item { - height: 16 - width:parent.width - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "button" } - Rectangle { height: parent.height; width: 16; color: sp.button } - Rectangle { height: parent.height; width: 16; color: spi.button } - Rectangle { height: parent.height; width: 16; color: spd.button } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "buttonText" } - Rectangle { height: parent.height; width: 16; color: sp.buttonText } - Rectangle { height: parent.height; width: 16; color: spi.buttonText } - Rectangle { height: parent.height; width: 16; color: spd.buttonText } - } - Item { - height: 16 - width:parent.width - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "highlight" } - Rectangle { height: parent.height; width: 16; color: sp.highlight } - Rectangle { height: parent.height; width: 16; color: spi.highlight } - Rectangle { height: parent.height; width: 16; color: spd.highlight } - } - Row { - width: parent.width - height: 16 - Text { height: parent.height; width: 128; text: "highlighted text" } - Rectangle { height: parent.height; width: 16; color: sp.highlightedText} - Rectangle { height: parent.height; width: 16; color: spi.highlightedText} - Rectangle { height: parent.height; width: 16; color: spd.highlightedText} - } - } -} diff --git a/tests-manual/ui/qml/ScrollingGraph.qml b/tests-manual/ui/qml/ScrollingGraph.qml deleted file mode 100644 index 55523a23f4..0000000000 --- a/tests-manual/ui/qml/ScrollingGraph.qml +++ /dev/null @@ -1,111 +0,0 @@ -import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 - -Rectangle { - id: root - property int size: 64 - width: size - height: size - color: 'black' - property int controlId: 0 - property real value: 0.5 - property int scrollWidth: 1 - property real min: 0.0 - property real max: 1.0 - property bool log: false - property real range: max - min - property color lineColor: 'yellow' - property bool bar: false - property real lastHeight: -1 - property string label: "" - - function update() { - value = Controller.getValue(controlId); - if (log) { - var log = Math.log(10) / Math.log(Math.abs(value)); - var sign = Math.sign(value); - value = log * sign; - } - canvas.requestPaint(); - } - - function drawHeight() { - if (value < min) { - return 0; - } - if (value > max) { - return height; - } - return ((value - min) / range) * height; - } - - Timer { - interval: 50; running: true; repeat: true - onTriggered: root.update() - } - - Canvas { - id: canvas - anchors.fill: parent - antialiasing: false - - Text { - anchors.top: parent.top - text: root.label - color: 'white' - } - - Text { - anchors.right: parent.right - anchors.top: parent.top - text: root.max - color: 'white' - } - - Text { - anchors.right: parent.right - anchors.bottom: parent.bottom - text: root.min - color: 'white' - } - - function scroll() { - var ctx = canvas.getContext('2d'); - var image = ctx.getImageData(0, 0, canvas.width, canvas.height); - ctx.beginPath(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(image, -root.scrollWidth, 0, canvas.width, canvas.height) - ctx.restore() - } - - onPaint: { - scroll(); - var ctx = canvas.getContext('2d'); - ctx.save(); - var currentHeight = root.drawHeight(); - if (root.lastHeight == -1) { - root.lastHeight = currentHeight - } - -// var x = canvas.width - root.drawWidth; -// var y = canvas.height - drawHeight; -// ctx.fillStyle = root.color -// ctx.fillRect(x, y, root.drawWidth, root.bar ? drawHeight : 1) -// ctx.fill(); -// ctx.restore() - - - ctx.beginPath(); - ctx.lineWidth = 1 - ctx.strokeStyle = root.lineColor - ctx.moveTo(canvas.width - root.scrollWidth, root.lastHeight).lineTo(canvas.width, currentHeight) - ctx.stroke() - ctx.restore() - root.lastHeight = currentHeight - } - } -} - - diff --git a/tests-manual/ui/qml/StubMenu.qml b/tests-manual/ui/qml/StubMenu.qml deleted file mode 100644 index fd0298988a..0000000000 --- a/tests-manual/ui/qml/StubMenu.qml +++ /dev/null @@ -1,730 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 - -import "../../../interface/resources/qml/hifi" - -Menu { - property var menuOption: MenuOption {} - Item { - Action { - id: login; - text: menuOption.login - } - Action { - id: update; - text: "Update"; - enabled: false - } - Action { - id: crashReporter; - text: "Crash Reporter..."; - enabled: false - } - Action { - id: help; - text: menuOption.help - onTriggered: Application.showHelp() - } - Action { - id: aboutApp; - text: menuOption.aboutApp - } - Action { - id: quit; - text: menuOption.quit - } - - ExclusiveGroup { id: renderResolutionGroup } - Action { - id: renderResolutionOne; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionOne; - checkable: true; - checked: true - } - Action { - id: renderResolutionTwoThird; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionTwoThird; - checkable: true - } - Action { - id: renderResolutionHalf; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionHalf; - checkable: true - } - Action { - id: renderResolutionThird; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionThird; - checkable: true - } - Action { - id: renderResolutionQuarter; - exclusiveGroup: renderResolutionGroup; - text: menuOption.renderResolutionQuarter; - checkable: true - } - - ExclusiveGroup { id: ambientLightGroup } - Action { - id: renderAmbientLightGlobal; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLightGlobal; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight0; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight0; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight1; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight1; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight2; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight2; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight3; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight3; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight4; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight4; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight5; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight5; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight6; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight6; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight7; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight7; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight8; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight8; - checkable: true; - checked: true - } - Action { - id: renderAmbientLight9; - exclusiveGroup: ambientLightGroup; - text: menuOption.renderAmbientLight9; - checkable: true; - checked: true - } - Action { - id: preferences - shortcut: StandardKey.Preferences - text: menuOption.preferences - onTriggered: dialogsManager.editPreferences() - } - - } - - Menu { - title: "File" - MenuItem { - action: login - } - MenuItem { - action: update - } - MenuItem { - action: help - } - MenuItem { - action: crashReporter - } - MenuItem { - action: aboutApp - } - MenuItem { - action: quit - } - } - - Menu { - title: "Edit" - MenuItem { - text: "Undo" } - MenuItem { - text: "Redo" } - MenuItem { - text: menuOption.runningScripts - } - MenuItem { - text: menuOption.loadScript - } - MenuItem { - text: menuOption.loadScriptURL - } - MenuItem { - text: menuOption.stopAllScripts - } - MenuItem { - text: menuOption.reloadAllScripts - } - MenuItem { - text: menuOption.scriptEditor - } - MenuItem { - text: menuOption.console_ - } - MenuItem { - text: menuOption.reloadContent - } - MenuItem { - text: menuOption.packageModel - } - } - - Menu { - title: "Audio" - MenuItem { - text: menuOption.muteAudio; - checkable: true - } - MenuItem { - text: menuOption.audioTools; - checkable: true - } - } - Menu { - title: "Avatar" - // Avatar > Attachments... - MenuItem { - text: menuOption.attachments - } - Menu { - title: "Size" - // Avatar > Size > Increase - MenuItem { - text: menuOption.increaseAvatarSize - } - // Avatar > Size > Decrease - MenuItem { - text: menuOption.decreaseAvatarSize - } - // Avatar > Size > Reset - MenuItem { - text: menuOption.resetAvatarSize - } - } - // Avatar > Reset Sensors - MenuItem { - text: menuOption.resetSensors - } - } - Menu { - title: "Display" - } - Menu { - title: "View" - ExclusiveGroup { - id: cameraModeGroup - } - - MenuItem { - text: menuOption.firstPerson; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuItem { - text: menuOption.thirdPerson; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuItem { - text: menuOption.fullscreenMirror; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuItem { - text: menuOption.independentMode; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuItem { - text: menuOption.cameraEntityMode; - checkable: true; - exclusiveGroup: cameraModeGroup - } - MenuSeparator{} - MenuItem { - text: menuOption.miniMirror; - checkable: true - } - } - Menu { - title: "Navigate" - MenuItem { - text: "Home" } - MenuItem { - text: menuOption.addressBar - } - MenuItem { - text: "Directory" } - MenuItem { - text: menuOption.copyAddress - } - MenuItem { - text: menuOption.copyPath - } - } - Menu { - title: "Settings" - MenuItem { - text: "Advanced Menus" } - MenuItem { - text: "Developer Menus" } - MenuItem { - text: menuOption.preferences - } - MenuItem { - text: "Avatar..." } - MenuItem { - text: "Audio..." } - MenuItem { - text: "LOD..." } - MenuItem { - text: menuOption.inputMenu - } - } - Menu { - title: "Developer" - Menu { - title: "Render" - MenuItem { - text: menuOption.atmosphere; - checkable: true - } - MenuItem { - text: menuOption.worldAxes; - checkable: true - } - MenuItem { - text: menuOption.debugAmbientOcclusion; - checkable: true - } - MenuItem { - text: menuOption.antialiasing; - checkable: true - } - MenuItem { - text: menuOption.stars; - checkable: true - } - Menu { - title: menuOption.renderAmbientLight - MenuItem { - action: renderAmbientLightGlobal; } - MenuItem { - action: renderAmbientLight0; } - MenuItem { - action: renderAmbientLight1; } - MenuItem { - action: renderAmbientLight2; } - MenuItem { - action: renderAmbientLight3; } - MenuItem { - action: renderAmbientLight4; } - MenuItem { - action: renderAmbientLight5; } - MenuItem { - action: renderAmbientLight6; } - MenuItem { - action: renderAmbientLight7; } - MenuItem { - action: renderAmbientLight8; } - MenuItem { - action: renderAmbientLight9; } - } - MenuItem { - text: menuOption.throttleFPSIfNotFocus; - checkable: true - } - Menu { - title: menuOption.renderResolution - MenuItem { - action: renderResolutionOne - } - MenuItem { - action: renderResolutionTwoThird - } - MenuItem { - action: renderResolutionHalf - } - MenuItem { - action: renderResolutionThird - } - MenuItem { - action: renderResolutionQuarter - } - } - MenuItem { - text: menuOption.lodTools - } - } - Menu { - title: "Assets" - MenuItem { - text: menuOption.uploadAsset - } - MenuItem { - text: menuOption.assetMigration - } - } - Menu { - title: "Avatar" - Menu { - title: "Face Tracking" - MenuItem { - text: menuOption.noFaceTracking; - checkable: true - } - MenuItem { - text: menuOption.faceshift; - checkable: true - } - MenuItem { - text: menuOption.useCamera; - checkable: true - } - MenuSeparator{} - MenuItem { - text: menuOption.binaryEyelidControl; - checkable: true - } - MenuItem { - text: menuOption.coupleEyelids; - checkable: true - } - MenuItem { - text: menuOption.useAudioForMouth; - checkable: true - } - MenuItem { - text: menuOption.velocityFilter; - checkable: true - } - MenuItem { - text: menuOption.calibrateCamera - } - MenuSeparator{} - MenuItem { - text: menuOption.muteFaceTracking; - checkable: true - } - MenuItem { - text: menuOption.autoMuteAudio; - checkable: true - } - } - Menu { - title: "Eye Tracking" - MenuItem { - text: menuOption.sMIEyeTracking; - checkable: true - } - Menu { - title: "Calibrate" - MenuItem { - text: menuOption.onePointCalibration - } - MenuItem { - text: menuOption.threePointCalibration - } - MenuItem { - text: menuOption.fivePointCalibration - } - } - MenuItem { - text: menuOption.simulateEyeTracking; - checkable: true - } - } - MenuItem { - text: menuOption.avatarReceiveStats; - checkable: true - } - MenuItem { - text: menuOption.renderBoundingCollisionShapes; - checkable: true - } - MenuItem { - text: menuOption.renderLookAtVectors; - checkable: true - } - MenuItem { - text: menuOption.renderLookAtTargets; - checkable: true - } - MenuItem { - text: menuOption.renderFocusIndicator; - checkable: true - } - MenuItem { - text: menuOption.showWhosLookingAtMe; - checkable: true - } - MenuItem { - text: menuOption.fixGaze; - checkable: true - } - MenuItem { - text: menuOption.animDebugDrawDefaultPose; - checkable: true - } - MenuItem { - text: menuOption.animDebugDrawAnimPose; - checkable: true - } - MenuItem { - text: menuOption.animDebugDrawPosition; - checkable: true - } - MenuItem { - text: menuOption.meshVisible; - checkable: true - } - MenuItem { - text: menuOption.disableEyelidAdjustment; - checkable: true - } - MenuItem { - text: menuOption.turnWithHead; - checkable: true - } - MenuItem { - text: menuOption.keyboardMotorControl; - checkable: true - } - MenuItem { - text: menuOption.scriptedMotorControl; - checkable: true - } - MenuItem { - text: menuOption.enableCharacterController; - checkable: true - } - } - Menu { - title: "Hands" - MenuItem { - text: menuOption.displayHandTargets; - checkable: true - } - MenuItem { - text: menuOption.lowVelocityFilter; - checkable: true - } - Menu { - title: "Leap Motion" - MenuItem { - text: menuOption.leapMotionOnHMD; - checkable: true - } - } - } - Menu { - title: "Entities" - MenuItem { - text: menuOption.octreeStats - } - MenuItem { - text: menuOption.showRealtimeEntityStats; - checkable: true - } - } - Menu { - title: "Network" - MenuItem { - text: menuOption.reloadContent - } - MenuItem { - text: menuOption.disableNackPackets; - checkable: true - } - MenuItem { - text: menuOption.disableActivityLogger; - checkable: true - } - MenuItem { - text: menuOption.cachesSize - } - MenuItem { - text: menuOption.diskCacheEditor - } - MenuItem { - text: menuOption.showDSConnectTable - } - MenuItem { - text: menuOption.bandwidthDetails - } - } - Menu { - title: "Timing" - Menu { - title: "Performance Timer" - MenuItem { - text: menuOption.displayDebugTimingDetails; - checkable: true - } - MenuItem { - text: menuOption.onlyDisplayTopTen; - checkable: true - } - MenuItem { - text: menuOption.expandUpdateTiming; - checkable: true - } - MenuItem { - text: menuOption.expandMyAvatarTiming; - checkable: true - } - MenuItem { - text: menuOption.expandMyAvatarSimulateTiming; - checkable: true - } - MenuItem { - text: menuOption.expandOtherAvatarTiming; - checkable: true - } - MenuItem { - text: menuOption.expandPaintGLTiming; - checkable: true - } - } - MenuItem { - text: menuOption.frameTimer; - checkable: true - } - MenuItem { - text: menuOption.runTimingTests - } - MenuItem { - text: menuOption.pipelineWarnings; - checkable: true - } - MenuItem { - text: menuOption.logExtraTimings; - checkable: true - } - MenuItem { - text: menuOption.suppressShortTimings; - checkable: true - } - } - Menu { - title: "Audio" - MenuItem { - text: menuOption.audioNoiseReduction; - checkable: true - } - MenuItem { - text: menuOption.echoServerAudio; - checkable: true - } - MenuItem { - text: menuOption.echoLocalAudio; - checkable: true - } - MenuItem { - text: menuOption.muteEnvironment - } - Menu { - title: "Audio" - MenuItem { - text: menuOption.audioScope; - checkable: true - } - MenuItem { - text: menuOption.audioScopePause; - checkable: true - } - Menu { - title: "Display Frames" - ExclusiveGroup { - id: audioScopeFramesGroup - } - MenuItem { - exclusiveGroup: audioScopeFramesGroup; - text: menuOption.audioScopeFiveFrames; - checkable: true - } - MenuItem { - exclusiveGroup: audioScopeFramesGroup; - text: menuOption.audioScopeTwentyFrames; - checkable: true - } - MenuItem { - exclusiveGroup: audioScopeFramesGroup; - text: menuOption.audioScopeFiftyFrames; - checkable: true - } - } - MenuItem { - text: menuOption.audioNetworkStats - } - } - } - Menu { - title: "Physics" - MenuItem { - text: menuOption.physicsShowOwned; - checkable: true - } - MenuItem { - text: menuOption.physicsShowHulls; - checkable: true - } - } - MenuItem { - text: menuOption.displayCrashOptions; - checkable: true - } - MenuItem { - text: menuOption.crashInterface - } - MenuItem { - text: menuOption.log - } - MenuItem { - text: menuOption.stats; - checkable: true - } - } -} diff --git a/tests-manual/ui/qml/Stubs.qml b/tests-manual/ui/qml/Stubs.qml deleted file mode 100644 index 8c1465d54c..0000000000 --- a/tests-manual/ui/qml/Stubs.qml +++ /dev/null @@ -1,346 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 - -// Stubs for the global service objects set by Interface.cpp when creating the UI -// This is useful for testing inside Qt creator where these services don't actually exist. -Item { - - Item { - objectName: "offscreenFlags" - property bool navigationFocused: false - } - - Item { - objectName: "urlHandler" - function fixupUrl(url) { return url; } - function canHandleUrl(url) { return false; } - function handleUrl(url) { return true; } - } - - Item { - objectName: "Account" - function isLoggedIn() { return true; } - function getUsername() { return "Jherico"; } - } - - Item { - objectName: "GL" - property string vendor: "" - } - - Item { - objectName: "ApplicationCompositor" - property bool reticleOverDesktop: true - } - - Item { - objectName: "Controller" - function getRecommendedOverlayRect() { - return Qt.rect(0, 0, 1920, 1080); - } - } - - Item { - objectName: "Preferences" - // List of categories obtained by logging categories as they are added in Interface in Preferences::addPreference(). - property var categories: [ - "Avatar Basics", "Snapshots", "Scripts", "Privacy", "Level of Detail Tuning", "Avatar Tuning", "Avatar Camera", - "Audio", "Octree", "HMD", "Sixense Controllers", "Graphics" - ] - } - - Item { - objectName: "ScriptDiscoveryService" - //property var scriptsModelFilter: scriptsModel - signal scriptCountChanged() - property var _runningScripts:[ - { name: "wireFrameTest.js", url: "foo/wireframetest.js", path: "foo/wireframetest.js", local: true }, - { name: "edit.js", url: "foo/edit.js", path: "foo/edit.js", local: false }, - { name: "listAllScripts.js", url: "foo/listAllScripts.js", path: "foo/listAllScripts.js", local: false }, - { name: "users.js", url: "foo/users.js", path: "foo/users.js", local: false }, - ] - - function getRunning() { - return _runningScripts; - } - } - - Item { - objectName: "HMD" - property bool active: false - } - - Item { - id: menuHelper - objectName: "MenuHelper" - - Component { - id: modelMaker - ListModel { } - } - - function toModel(menu, parent) { - if (!parent) { parent = menuHelper } - var result = modelMaker.createObject(parent); - if (menu.type !== MenuItemType.Menu) { - console.warn("Not a menu: " + menu); - return result; - } - - var items = menu.items; - for (var i = 0; i < items.length; ++i) { - var item = items[i]; - switch (item.type) { - case 2: - result.append({"name": item.title, "item": item}) - break; - case 1: - result.append({"name": item.text, "item": item}) - break; - case 0: - result.append({"name": "", "item": item}) - break; - } - } - return result; - } - - } - - Item { - objectName: "Desktop" - - property string _OFFSCREEN_ROOT_OBJECT_NAME: "desktopRoot"; - property string _OFFSCREEN_DIALOG_OBJECT_NAME: "topLevelWindow"; - - - function findChild(item, name) { - for (var i = 0; i < item.children.length; ++i) { - if (item.children[i].objectName === name) { - return item.children[i]; - } - } - return null; - } - - function findParent(item, name) { - while (item) { - if (item.objectName === name) { - return item; - } - item = item.parent; - } - return null; - } - - function findDialog(item) { - item = findParent(item, _OFFSCREEN_DIALOG_OBJECT_NAME); - return item; - } - - function closeDialog(item) { - item = findDialog(item); - if (item) { - item.visible = false - } else { - console.warn("Could not find top level dialog") - } - } - - function getDesktop(item) { - while (item) { - if (item.desktopRoot) { - break; - } - item = item.parent; - } - return item - } - - function raise(item) { - var desktop = getDesktop(item); - if (desktop) { - desktop.raise(item); - } - } - } - - Menu { - id: root - objectName: "rootMenu" - - Menu { - title: "Audio" - } - - Menu { - title: "Avatar" - } - - Menu { - title: "Display" - ExclusiveGroup { id: displayMode } - Menu { - title: "More Stuff" - - Menu { title: "Empty" } - - MenuItem { - text: "Do Nothing" - onTriggered: console.log("Nothing") - } - } - MenuItem { - text: "Oculus" - exclusiveGroup: displayMode - checkable: true - } - MenuItem { - text: "OpenVR" - exclusiveGroup: displayMode - checkable: true - } - MenuItem { - text: "OSVR" - exclusiveGroup: displayMode - checkable: true - } - MenuItem { - text: "2D Screen" - exclusiveGroup: displayMode - checkable: true - checked: true - } - MenuItem { - text: "3D Screen (Active)" - exclusiveGroup: displayMode - checkable: true - } - MenuItem { - text: "3D Screen (Passive)" - exclusiveGroup: displayMode - checkable: true - } - } - - Menu { - title: "View" - Menu { - title: "Camera Mode" - ExclusiveGroup { id: cameraMode } - MenuItem { - exclusiveGroup: cameraMode - text: "First Person"; - onTriggered: console.log(text + " checked " + checked) - checkable: true - checked: true - } - MenuItem { - exclusiveGroup: cameraMode - text: "Third Person"; - onTriggered: console.log(text) - checkable: true - } - MenuItem { - exclusiveGroup: cameraMode - text: "Independent Mode"; - onTriggered: console.log(text) - checkable: true - } - MenuItem { - exclusiveGroup: cameraMode - text: "Entity Mode"; - onTriggered: console.log(text) - enabled: false - checkable: true - } - MenuItem { - exclusiveGroup: cameraMode - text: "Fullscreen Mirror"; - onTriggered: console.log(text) - checkable: true - } - } - } - - Menu { - title: "Edit" - - MenuItem { - text: "Undo" - shortcut: "Ctrl+Z" - enabled: false - onTriggered: console.log(text) - } - - MenuItem { - text: "Redo" - shortcut: "Ctrl+Shift+Z" - enabled: false - onTriggered: console.log(text) - } - - MenuSeparator { } - - MenuItem { - text: "Cut" - shortcut: "Ctrl+X" - onTriggered: console.log(text) - } - - MenuItem { - text: "Copy" - shortcut: "Ctrl+C" - onTriggered: console.log(text) - } - - MenuItem { - text: "Paste" - shortcut: "Ctrl+V" - visible: false - onTriggered: console.log("Paste") - } - } - - Menu { - title: "Navigate" - } - - Menu { - title: "Market" - } - - Menu { - title: "Settings" - } - - Menu { - title: "Developer" - } - - Menu { - title: "Quit" - } - - Menu { - title: "File" - - Action { - id: login - text: "Login" - } - - Action { - id: quit - text: "Quit" - shortcut: "Ctrl+Q" - onTriggered: Qt.quit(); - } - - MenuItem { action: quit } - MenuItem { action: login } - } - } - -} - diff --git a/tests-manual/ui/qml/TestControllers.qml b/tests-manual/ui/qml/TestControllers.qml deleted file mode 100644 index e9a7fb49e5..0000000000 --- a/tests-manual/ui/qml/TestControllers.qml +++ /dev/null @@ -1,160 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 - -import "controller" -import "controls" as HifiControls -import "styles" - -HifiControls.VrDialog { - id: root - HifiConstants { id: hifi } - title: "Controller Test" - resizable: true - contentImplicitWidth: clientArea.implicitWidth - contentImplicitHeight: clientArea.implicitHeight - backgroundColor: "beige" - - property var actions: Controller.Actions - property var standard: Controller.Standard - property var hydra: null - property var testMapping: null - property bool testMappingEnabled: false - property var xbox: null - - function buildMapping() { - testMapping = Controller.newMapping(); - testMapping.fromQml(standard.RY).invert().toQml(actions.Pitch); - testMapping.fromQml(function(){ - return Math.sin(Date.now() / 250); - }).toQml(actions.Yaw); - //testMapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw); - // Step yaw takes a number of degrees - testMapping.fromQml(standard.LB).pulse(0.10).invert().scale(40.0).toQml(actions.StepYaw); - testMapping.fromQml(standard.RB).pulse(0.10).scale(15.0).toQml(actions.StepYaw); - testMapping.fromQml(standard.RX).scale(15.0).toQml(actions.StepYaw); - } - - function toggleMapping() { - testMapping.enable(!testMappingEnabled); - testMappingEnabled = !testMappingEnabled; - } - - Component.onCompleted: { - var xboxRegex = /^GamePad/; - var hydraRegex = /^Hydra/; - for (var prop in Controller.Hardware) { - if(xboxRegex.test(prop)) { - root.xbox = Controller.Hardware[prop] - print("found xbox") - continue - } - if (hydraRegex.test(prop)) { - root.hydra = Controller.Hardware[prop] - print("found hydra") - continue - } - } - } - - Column { - id: clientArea - spacing: 12 - x: root.clientX - y: root.clientY - - Row { - spacing: 8 - - Button { - text: !root.testMapping ? "Build Mapping" : (root.testMappingEnabled ? "Disable Mapping" : "Enable Mapping") - onClicked: { - - if (!root.testMapping) { - root.buildMapping() - } else { - root.toggleMapping(); - } - } - } - } - - Row { - Standard { device: root.standard; label: "Standard"; width: 180 } - } - - Row { - spacing: 8 - Xbox { device: root.xbox; label: "XBox"; width: 180 } - Hydra { device: root.hydra; width: 180 } - } - - Row { - spacing: 4 - ScrollingGraph { - controlId: Controller.Actions.Yaw - label: "Yaw" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.YawLeft - label: "Yaw Left" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.YawRight - label: "Yaw Right" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.StepYaw - label: "StepYaw" - min: -20.0 - max: 20.0 - size: 64 - } - } - - Row { - ScrollingGraph { - controlId: Controller.Actions.TranslateZ - label: "TranslateZ" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.Forward - label: "Forward" - min: -2.0 - max: 2.0 - size: 64 - } - - ScrollingGraph { - controlId: Controller.Actions.Backward - label: "Backward" - min: -2.0 - max: 2.0 - size: 64 - } - - } - } -} // dialog - - - - - diff --git a/tests-manual/ui/qml/TestDialog.qml b/tests-manual/ui/qml/TestDialog.qml deleted file mode 100644 index e6675b7282..0000000000 --- a/tests-manual/ui/qml/TestDialog.qml +++ /dev/null @@ -1,94 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.3 -import "controls" - -VrDialog { - title: "Test Dialog" - id: testDialog - objectName: "TestDialog" - width: 512 - height: 512 - animationDuration: 200 - - onEnabledChanged: { - if (enabled) { - edit.forceActiveFocus(); - } - } - - Item { - id: clientArea - // The client area - anchors.fill: parent - anchors.margins: parent.margins - anchors.topMargin: parent.topMargin - - Rectangle { - property int d: 100 - id: square - objectName: "testRect" - width: d - height: d - anchors.centerIn: parent - color: "red" - NumberAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; } - } - - - TextEdit { - id: edit - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.right: parent.right - anchors.rightMargin: 12 - clip: true - text: "test edit" - anchors.top: parent.top - anchors.topMargin: 12 - } - - Button { - x: 128 - y: 192 - text: "Test" - anchors.bottom: parent.bottom - anchors.bottomMargin: 12 - anchors.right: parent.right - anchors.rightMargin: 12 - onClicked: { - console.log("Click"); - - if (square.visible) { - square.visible = false - } else { - square.visible = true - } - } - } - - Button { - id: customButton2 - y: 192 - text: "Move" - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 12 - onClicked: { - onClicked: testDialog.x == 0 ? testDialog.x = 200 : testDialog.x = 0 - } - } - - Keys.onPressed: { - console.log("Key " + event.key); - switch (event.key) { - case Qt.Key_Q: - if (Qt.ControlModifier == event.modifiers) { - event.accepted = true; - break; - } - } - } - } -} diff --git a/tests-manual/ui/qml/TestMenu.qml b/tests-manual/ui/qml/TestMenu.qml deleted file mode 100644 index fe8a26e234..0000000000 --- a/tests-manual/ui/qml/TestMenu.qml +++ /dev/null @@ -1,10 +0,0 @@ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import Hifi 1.0 - -// Currently for testing a pure QML replacement menu -Item { - Menu { - objectName: "rootMenu"; - } -} diff --git a/tests-manual/ui/qml/TestRoot.qml b/tests-manual/ui/qml/TestRoot.qml deleted file mode 100644 index bd38c696bf..0000000000 --- a/tests-manual/ui/qml/TestRoot.qml +++ /dev/null @@ -1,43 +0,0 @@ -import Hifi 1.0 -import QtQuick 2.3 -import QtQuick.Controls 1.3 -// Import local folder last so that our own control customizations override -// the built in ones -import "controls" - -Root { - id: root - anchors.fill: parent - onParentChanged: { - forceActiveFocus(); - } - Button { - id: messageBox - anchors.right: createDialog.left - anchors.rightMargin: 24 - anchors.bottom: parent.bottom - anchors.bottomMargin: 24 - text: "Message" - onClicked: { - console.log("Foo") - root.information("a") - console.log("Bar") - } - } - Button { - id: createDialog - anchors.right: parent.right - anchors.rightMargin: 24 - anchors.bottom: parent.bottom - anchors.bottomMargin: 24 - text: "Create" - onClicked: { - root.loadChild("MenuTest.qml"); - } - } - - Keys.onPressed: { - console.log("Key press root") - } -} - diff --git a/tests-manual/ui/qml/controlDemo/ButtonPage.qml b/tests-manual/ui/qml/controlDemo/ButtonPage.qml deleted file mode 100644 index 0ed7e2d6ad..0000000000 --- a/tests-manual/ui/qml/controlDemo/ButtonPage.qml +++ /dev/null @@ -1,128 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.2 - -ScrollView { - id: page - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - Item { - id: content - - width: Math.max(page.viewport.width, grid.implicitWidth + 2 * grid.rowSpacing) - height: Math.max(page.viewport.height, grid.implicitHeight + 2 * grid.columnSpacing) - - GridLayout { - id: grid - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: grid.rowSpacing - anchors.rightMargin: grid.rowSpacing - anchors.topMargin: grid.columnSpacing - - columns: page.width < page.height ? 1 : 2 - - GroupBox { - title: "Button" - Layout.fillWidth: true - Layout.columnSpan: grid.columns - RowLayout { - anchors.fill: parent - Button { text: "OK"; isDefault: true } - Button { text: "Cancel" } - Item { Layout.fillWidth: true } - Button { - text: "Attach" - menu: Menu { - MenuItem { text: "Image" } - MenuItem { text: "Document" } - } - } - } - } - - GroupBox { - title: "CheckBox" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - CheckBox { text: "E-mail"; checked: true } - CheckBox { text: "Calendar"; checked: true } - CheckBox { text: "Contacts" } - } - } - - GroupBox { - title: "RadioButton" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - ExclusiveGroup { id: radioGroup } - RadioButton { text: "Portrait"; exclusiveGroup: radioGroup } - RadioButton { text: "Landscape"; exclusiveGroup: radioGroup } - RadioButton { text: "Automatic"; exclusiveGroup: radioGroup; checked: true } - } - } - - GroupBox { - title: "Switch" - Layout.fillWidth: true - Layout.columnSpan: grid.columns - ColumnLayout { - anchors.fill: parent - RowLayout { - Label { text: "Wi-Fi"; Layout.fillWidth: true } - Switch { checked: true } - } - RowLayout { - Label { text: "Bluetooth"; Layout.fillWidth: true } - Switch { checked: false } - } - } - } - } - } -} diff --git a/tests-manual/ui/qml/controlDemo/InputPage.qml b/tests-manual/ui/qml/controlDemo/InputPage.qml deleted file mode 100644 index cb1878d023..0000000000 --- a/tests-manual/ui/qml/controlDemo/InputPage.qml +++ /dev/null @@ -1,114 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.2 - -ScrollView { - id: page - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - Item { - id: content - - width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) - height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) - - ColumnLayout { - id: column - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: column.spacing - - GroupBox { - title: "TextField" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - TextField { placeholderText: "..."; Layout.fillWidth: true; z: 1 } - TextField { placeholderText: "Password"; echoMode: TextInput.Password; Layout.fillWidth: true } - } - } - - GroupBox { - title: "ComboBox" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - ComboBox { - model: Qt.fontFamilies() - Layout.fillWidth: true - } - ComboBox { - editable: true - model: ListModel { - id: listModel - ListElement { text: "Apple" } - ListElement { text: "Banana" } - ListElement { text: "Coconut" } - ListElement { text: "Orange" } - } - onAccepted: { - if (find(currentText) === -1) { - listModel.append({text: editText}) - currentIndex = find(editText) - } - } - Layout.fillWidth: true - } - } - } - - GroupBox { - title: "SpinBox" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - SpinBox { value: 99; Layout.fillWidth: true; z: 1 } - SpinBox { decimals: 2; Layout.fillWidth: true } - } - } - } - } -} diff --git a/tests-manual/ui/qml/controlDemo/ProgressPage.qml b/tests-manual/ui/qml/controlDemo/ProgressPage.qml deleted file mode 100644 index a1fa596f79..0000000000 --- a/tests-manual/ui/qml/controlDemo/ProgressPage.qml +++ /dev/null @@ -1,90 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 1.2 - -ScrollView { - id: page - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - Item { - id: content - - width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) - height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) - - ColumnLayout { - id: column - - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: column.spacing - - GroupBox { - title: "ProgressBar" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - ProgressBar { indeterminate: true; Layout.fillWidth: true } - ProgressBar { value: slider.value; Layout.fillWidth: true } - } - } - - GroupBox { - title: "Slider" - Layout.fillWidth: true - ColumnLayout { - anchors.fill: parent - Slider { id: slider; value: 0.5; Layout.fillWidth: true } - } - } - - GroupBox { - title: "BusyIndicator" - Layout.fillWidth: true - BusyIndicator { running: true } - } - } - } -} diff --git a/tests-manual/ui/qml/controlDemo/main.qml b/tests-manual/ui/qml/controlDemo/main.qml deleted file mode 100644 index 168b9fb291..0000000000 --- a/tests-manual/ui/qml/controlDemo/main.qml +++ /dev/null @@ -1,161 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Quick Controls module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.2 -import QtQuick.Layouts 1.1 -import QtQuick.Dialogs 1.1 -import QtQuick.Controls 1.2 -import "qml/UI.js" as UI -import "qml" -//import "/Users/bdavis/Git/hifi/interface/resources/qml" - -Item { - anchors.fill: parent - visible: true - //title: "Qt Quick Controls Gallery" - - MessageDialog { - id: aboutDialog - icon: StandardIcon.Information - title: "About" - text: "Qt Quick Controls Gallery" - informativeText: "This example demonstrates most of the available Qt Quick Controls." - } - - Action { - id: copyAction - text: "&Copy" - shortcut: StandardKey.Copy - iconName: "edit-copy" - enabled: (!!activeFocusItem && !!activeFocusItem["copy"]) - onTriggered: activeFocusItem.copy() - } - - Action { - id: cutAction - text: "Cu&t" - shortcut: StandardKey.Cut - iconName: "edit-cut" - enabled: (!!activeFocusItem && !!activeFocusItem["cut"]) - onTriggered: activeFocusItem.cut() - } - - Action { - id: pasteAction - text: "&Paste" - shortcut: StandardKey.Paste - iconName: "edit-paste" - enabled: (!!activeFocusItem && !!activeFocusItem["paste"]) - onTriggered: activeFocusItem.paste() - } - -// toolBar: ToolBar { -// RowLayout { -// anchors.fill: parent -// anchors.margins: spacing -// Label { -// text: UI.label -// } -// Item { Layout.fillWidth: true } -// CheckBox { -// id: enabler -// text: "Enabled" -// checked: true -// } -// } -// } - -// menuBar: MenuBar { -// Menu { -// title: "&File" -// MenuItem { -// text: "E&xit" -// shortcut: StandardKey.Quit -// onTriggered: Qt.quit() -// } -// } -// Menu { -// title: "&Edit" -// visible: tabView.currentIndex == 2 -// MenuItem { action: cutAction } -// MenuItem { action: copyAction } -// MenuItem { action: pasteAction } -// } -// Menu { -// title: "&Help" -// MenuItem { -// text: "About..." -// onTriggered: aboutDialog.open() -// } -// } -// } - - TabView { - id: tabView - - anchors.fill: parent - anchors.margins: UI.margin - tabPosition: UI.tabPosition - - Layout.minimumWidth: 360 - Layout.minimumHeight: 360 - Layout.preferredWidth: 480 - Layout.preferredHeight: 640 - - Tab { - title: "Buttons" - ButtonPage { - enabled: enabler.checked - } - } - Tab { - title: "Progress" - ProgressPage { - enabled: enabler.checked - } - } - Tab { - title: "Input" - InputPage { - enabled: enabler.checked - } - } - } -} diff --git a/tests-manual/ui/qml/main.qml b/tests-manual/ui/qml/main.qml deleted file mode 100644 index 607bd624a1..0000000000 --- a/tests-manual/ui/qml/main.qml +++ /dev/null @@ -1,461 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 as OriginalDialogs -import Qt.labs.settings 1.0 - -import "../../../interface/resources/qml" -//import "../../../interface/resources/qml/windows" -import "../../../interface/resources/qml/windows" -import "../../../interface/resources/qml/dialogs" -import "../../../interface/resources/qml/hifi" -import "../../../interface/resources/qml/hifi/dialogs" -import "../../../interface/resources/qml/styles-uit" - -ApplicationWindow { - id: appWindow - objectName: "MainWindow" - visible: true - width: 1280 - height: 800 - title: qsTr("Scratch App") - toolBar: Row { - id: testButtons - anchors { margins: 8; left: parent.left; top: parent.top } - spacing: 8 - property int count: 0 - - property var tabs: []; - property var urls: []; - property var toolbar; - property var lastButton; - - Button { - text: HMD.active ? "Disable HMD" : "Enable HMD" - onClicked: HMD.active = !HMD.active - } - - Button { - text: desktop.hmdHandMouseActive ? "Disable HMD HandMouse" : "Enable HMD HandMouse" - onClicked: desktop.hmdHandMouseActive = !desktop.hmdHandMouseActive - } - - // Window visibility - Button { - text: "toggle desktop" - onClicked: desktop.togglePinned() - } - - Button { - text: "Create Toolbar" - onClicked: testButtons.toolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); - } - - Button { - text: "Toggle Toolbar Direction" - onClicked: testButtons.toolbar.horizontal = !testButtons.toolbar.horizontal - } - - Button { - readonly property var icons: [ - "edit-01.svg", - "model-01.svg", - "cube-01.svg", - "sphere-01.svg", - "light-01.svg", - "text-01.svg", - "web-01.svg", - "zone-01.svg", - "particle-01.svg", - ] - property int iconIndex: 0 - readonly property string toolIconUrl: "../../../../../scripts/system/assets/images/tools/" - text: "Create Button" - onClicked: { - var name = icons[iconIndex]; - var url = toolIconUrl + name; - iconIndex = (iconIndex + 1) % icons.length; - var button = testButtons.lastButton = testButtons.toolbar.addButton({ - imageURL: url, - objectName: name, - subImage: { - y: 50, - }, - alpha: 0.9 - }); - - button.clicked.connect(function(){ - console.log("Clicked on button " + button.imageURL + " alpha " + button.alpha) - }); - } - } - - Button { - text: "Toggle Button Visible" - onClicked: testButtons.lastButton.visible = !testButtons.lastButton.visible - } - - // Error alerts - /* - Button { - // Message without title. - text: "Show Error" - onClicked: { - var messageBox = desktop.messageBox({ - text: "Diagnostic cycle will be complete in 30 seconds", - icon: hifi.icons.critical, - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - Button { - // detailedText is not currently used anywhere in Interface but it is easier to leave in and style good enough. - text: "Show Long Error" - onClicked: { - desktop.messageBox({ - informativeText: "Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds ", - text: "Baloney", - icon: hifi.icons.warning, - detailedText: "sakjd;laskj dksa;dl jka;lsd j;lkjas ;dlkaj s;dlakjd ;alkjda; slkjda; lkjda;lksjd ;alksjd; alksjd ;alksjd; alksjd; alksdjas;ldkjas;lkdja ;kj ;lkasjd; lkj as;dlka jsd;lka jsd;laksjd a" - }); - } - } - */ - - // query - /* - // There is no such desktop.queryBox() function; may need to update test to cover QueryDialog.qml? - Button { - text: "Show Query" - onClicked: { - var queryBox = desktop.queryBox({ - text: "Have you stopped beating your wife?", - placeholderText: "Are you sure?", - // icon: hifi.icons.critical, - }); - queryBox.selected.connect(function(result) { - console.log("User responded with " + result); - }); - - queryBox.canceled.connect(function() { - console.log("User cancelled query box "); - }) - } - } - */ - - // Browser - /* - Button { - text: "Open Browser" - onClicked: builder.createObject(desktop); - property var builder: Component { - Browser {} - } - } - */ - - - // file dialog - /* - - Button { - text: "Open Directory" - property var builder: Component { - FileDialog { selectDirectory: true } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - Button { - text: "Open File" - property var builder: Component { - FileDialog { - title: "Open File" - filter: "All Files (*.*)" - //filter: "HTML files (*.html);;Other(*.png)" - } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - */ - - // tabs - /* - Button { - text: "Add Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo" }); - desktop.toolWindow.showTabForUrl("Foo", true); - } - } - - Button { - text: "Add Tab 2" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 2" }); - desktop.toolWindow.showTabForUrl("Foo 2", true); - } - } - - Button { - text: "Add Tab 3" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 3" }); - desktop.toolWindow.showTabForUrl("Foo 3", true); - } - } - - Button { - text: "Destroy Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.removeTabForUrl("Foo"); - } - } - */ - - // Hifi specific stuff - /* - Button { - // Shows the dialog with preferences sections but not each section's preference items - // because Preferences.preferencesByCategory() method is not stubbed out. - text: "Settings > General..." - property var builder: Component { - GeneralPreferencesDialog { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Running Scripts" - property var builder: Component { - RunningScripts { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Attachments" - property var builder: Component { - AttachmentsDialog { } - } - onClicked: { - var attachmentsDialog = builder.createObject(desktop); - } - } - Button { - // Replicates message box that pops up after selecting new avatar. Includes title. - text: "Confirm Avatar" - onClicked: { - var messageBox = desktop.messageBox({ - title: "Set Avatar", - text: "Would you like to use 'Albert' for your avatar?", - icon: hifi.icons.question, // Test question icon - //icon: hifi.icons.information, // Test informaton icon - //icon: hifi.icons.warning, // Test warning icon - //icon: hifi.icons.critical, // Test critical icon - //icon: hifi.icons.none, // Test no icon - buttons: OriginalDialogs.StandardButton.Ok + OriginalDialogs.StandardButton.Cancel, - defaultButton: OriginalDialogs.StandardButton.Ok - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - */ - // bookmarks - /* - Button { - text: "Bookmark Location" - onClicked: { - desktop.inputDialog({ - title: "Bookmark Location", - icon: hifi.icons.placemark, - label: "Name" - }); - } - } - Button { - text: "Delete Bookmark" - onClicked: { - desktop.inputDialog({ - title: "Delete Bookmark", - icon: hifi.icons.placemark, - label: "Select the bookmark to delete", - items: ["Bookmark A", "Bookmark B", "Bookmark C"] - }); - } - } - Button { - text: "Duplicate Bookmark" - onClicked: { - desktop.messageBox({ - title: "Duplicate Bookmark", - icon: hifi.icons.warning, - text: "The bookmark name you entered alread exists in yoru list.", - informativeText: "Would you like to overwrite it?", - buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, - defaultButton: OriginalDialogs.StandardButton.Yes - }); - } - } - */ - - } - - - HifiConstants { id: hifi } - - Desktop { - id: desktop - anchors.fill: parent - - //rootMenu: StubMenu { id: rootMenu } - //Component.onCompleted: offscreenWindow = appWindow - - /* - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: desktop.popupMenu(Qt.vector2d(mouseX, mouseY)); - } - */ - - Browser { - url: "http://s3.amazonaws.com/DreamingContent/testUiDelegates.html" - } - - - Window { - id: blue - closable: true - visible: true - resizable: true - destroyOnHidden: false - title: "Blue" - - width: 100; height: 100 - x: 1280 / 2; y: 720 / 2 - Settings { - category: "TestWindow.Blue" - property alias x: blue.x - property alias y: blue.y - property alias width: blue.width - property alias height: blue.height - } - - Rectangle { - anchors.fill: parent - visible: true - color: "blue" - Component.onDestruction: console.log("Blue destroyed") - } - } - - Window { - id: green - closable: true - visible: true - resizable: true - title: "Green" - destroyOnHidden: false - - width: 100; height: 100 - x: 1280 / 2; y: 720 / 2 - Settings { - category: "TestWindow.Green" - property alias x: green.x - property alias y: green.y - property alias width: green.width - property alias height: green.height - } - - Rectangle { - anchors.fill: parent - visible: true - color: "green" - Component.onDestruction: console.log("Green destroyed") - } - } - -/* - Rectangle { width: 100; height: 100; x: 100; y: 100; color: "#00f" } - - Window { - id: green - alwaysOnTop: true - frame: HiddenFrame{} - hideBackground: true - closable: true - visible: true - resizable: false - x: 1280 / 2; y: 720 / 2 - width: 100; height: 100 - Rectangle { - color: "#0f0" - width: green.width; - height: green.height; - } - } - */ - -/* - Window { - id: yellow - closable: true - visible: true - resizable: true - x: 100; y: 100 - width: 100; height: 100 - Rectangle { - anchors.fill: parent - visible: true - color: "yellow" - } - } -*/ - } - - Action { - id: openBrowserAction - text: "Open Browser" - shortcut: "Ctrl+Shift+X" - onTriggered: { - builder.createObject(desktop); - } - property var builder: Component { - ModelBrowserDialog{} - } - } -} - - - - diff --git a/tests-manual/ui/qmlscratch.pro b/tests-manual/ui/qmlscratch.pro index 5c9b91ee52..3287180d26 100644 --- a/tests-manual/ui/qmlscratch.pro +++ b/tests-manual/ui/qmlscratch.pro @@ -4,34 +4,10 @@ QT += gui qml quick xml webengine widgets CONFIG += c++11 -SOURCES += src/main.cpp \ - ../../libraries/ui/src/FileDialogHelper.cpp - -HEADERS += \ - ../../libraries/ui/src/FileDialogHelper.h +SOURCES += src/main.cpp # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = ../../interface/resources/qml - DISTFILES += \ - qml/*.qml \ - ../../interface/resources/QtWebEngine/UIDelegates/original/*.qml \ - ../../interface/resources/QtWebEngine/UIDelegates/*.qml \ - ../../interface/resources/qml/*.qml \ - ../../interface/resources/qml/controls/*.qml \ - ../../interface/resources/qml/controls-uit/*.qml \ - ../../interface/resources/qml/dialogs/*.qml \ - ../../interface/resources/qml/dialogs/fileDialog/*.qml \ - ../../interface/resources/qml/dialogs/preferences/*.qml \ - ../../interface/resources/qml/dialogs/messageDialog/*.qml \ - ../../interface/resources/qml/desktop/*.qml \ - ../../interface/resources/qml/menus/*.qml \ - ../../interface/resources/qml/styles/*.qml \ - ../../interface/resources/qml/styles-uit/*.qml \ - ../../interface/resources/qml/windows/*.qml \ - ../../interface/resources/qml/hifi/*.qml \ - ../../interface/resources/qml/hifi/toolbars/*.qml \ - ../../interface/resources/qml/hifi/dialogs/*.qml \ - ../../interface/resources/qml/hifi/dialogs/preferences/*.qml \ - ../../interface/resources/qml/hifi/overlays/*.qml + qml/*.qml diff --git a/tests-manual/ui/src/main.cpp b/tests-manual/ui/src/main.cpp index 312b5f3823..a5061f4d01 100644 --- a/tests-manual/ui/src/main.cpp +++ b/tests-manual/ui/src/main.cpp @@ -3,88 +3,31 @@ #include #include -#include "../../../libraries/ui/src/FileDialogHelper.h" - - -class Preference : public QObject { - Q_OBJECT - Q_PROPERTY(QString category READ getCategory() CONSTANT) - Q_PROPERTY(QString name READ getName() CONSTANT) - Q_PROPERTY(Type type READ getType() CONSTANT) - Q_ENUMS(Type) -public: - enum Type { - Editable, - Browsable, - Spinner, - Checkbox, - }; - - Preference(QObject* parent = nullptr) : QObject(parent) {} - - Preference(const QString& category, const QString& name, QObject* parent = nullptr) - : QObject(parent), _category(category), _name(name) { } - const QString& getCategory() const { return _category; } - const QString& getName() const { return _name; } - virtual Type getType() { return Editable; } - -protected: - const QString _category; - const QString _name; -}; - -class Reticle : public QObject { - Q_OBJECT - Q_PROPERTY(QPoint position READ getPosition CONSTANT) -public: - - Reticle(QObject* parent) : QObject(parent) { - } - - QPoint getPosition() { - if (!_window) { - return QPoint(0, 0); - } - return _window->mapFromGlobal(QCursor::pos()); - } - - void setWindow(QWindow* window) { - _window = window; - } - -private: - QWindow* _window{nullptr}; -}; - QString getRelativeDir(const QString& relativePath = ".") { - QDir path(__FILE__); path.cdUp(); + QDir path(__FILE__); + path.cdUp(); + path.cdUp(); auto result = path.absoluteFilePath(relativePath); result = path.cleanPath(result) + "/"; return result; } -QString getTestQmlDir() { - return getRelativeDir("../qml"); +QString getResourcesDir() { + return getRelativeDir("../../interface/resources"); } -QString getInterfaceQmlDir() { - return getRelativeDir("/"); +QString getQmlDir() { + return getRelativeDir("../../interface/resources/qml"); } - -void setChild(QQmlApplicationEngine& engine, const char* name) { - for (auto obj : engine.rootObjects()) { - auto child = obj->findChild(QString(name)); - if (child) { - engine.rootContext()->setContextProperty(name, child); - return; - } - } - qWarning() << "Could not find object named " << name; +QString getScriptsDir() { + return getRelativeDir("../../scripts"); } void addImportPath(QQmlApplicationEngine& engine, const QString& relativePath, bool insert = false) { QString resolvedPath = getRelativeDir(relativePath); + + qDebug() << "adding import path: " << QDir::toNativeSeparators(resolvedPath); engine.addImportPath(resolvedPath); } @@ -93,44 +36,24 @@ int main(int argc, char *argv[]) { app.setOrganizationName("Some Company"); app.setOrganizationDomain("somecompany.com"); app.setApplicationName("Amazing Application"); - QDir::setCurrent(getRelativeDir("..")); - QtWebEngine::initialize(); - qmlRegisterType("Hifi", 1, 0, "Preference"); + auto scriptsDir = getScriptsDir(); + auto resourcesDir = getResourcesDir(); QQmlApplicationEngine engine; + addImportPath(engine, "."); addImportPath(engine, "qml"); - addImportPath(engine, "../../interface/resources/qml"); - addImportPath(engine, "../../interface/resources"); - engine.load(QUrl(QStringLiteral("qml/Stubs.qml"))); + addImportPath(engine, resourcesDir); + addImportPath(engine, resourcesDir + "/qml"); + addImportPath(engine, scriptsDir); + addImportPath(engine, scriptsDir + "/developer/tests"); - setChild(engine, "offscreenFlags"); - setChild(engine, "Account"); - setChild(engine, "ApplicationCompositor"); - setChild(engine, "Controller"); - setChild(engine, "Desktop"); - setChild(engine, "ScriptDiscoveryService"); - setChild(engine, "HMD"); - setChild(engine, "GL"); - setChild(engine, "MenuHelper"); - setChild(engine, "Preferences"); - setChild(engine, "urlHandler"); - engine.rootContext()->setContextProperty("DebugQML", true); - engine.rootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); + QFontDatabase::addApplicationFont(resourcesDir + "/fonts/FiraSans-Regular.ttf"); + QFontDatabase::addApplicationFont(resourcesDir + "/fonts/FiraSans-SemiBold.ttf"); + QFontDatabase::addApplicationFont(resourcesDir + "/fonts/hifi-glyphs.ttf"); - //engine.load(QUrl(QStringLiteral("qrc:/qml/gallery/main.qml"))); - engine.load(QUrl(QStringLiteral("qml/main.qml"))); - for (QObject* rootObject : engine.rootObjects()) { - if (rootObject->objectName() == "MainWindow") { - Reticle* reticle = new Reticle(rootObject); - reticle->setWindow((QWindow*)rootObject); - engine.rootContext()->setContextProperty("Reticle", reticle); - engine.rootContext()->setContextProperty("Window", rootObject); - break; - } - } + auto url = getRelativeDir(".") + "qml/ControlsGalleryWindow.qml"; + + engine.load(url); return app.exec(); } - -#include "main.moc" - From aa8da5ade81f2e3aa92043a372cb4d6591447e9b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 28 Jun 2018 16:58:45 -0700 Subject: [PATCH 136/276] Adding potential fix for showing audio stats - depends on PR#13401 --- scripts/developer/utilities/audio/Stats.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/developer/utilities/audio/Stats.qml b/scripts/developer/utilities/audio/Stats.qml index f359e9b04c..e2291e485d 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 "qrc:////qml//controls-uit" as HifiControls +import controlsUit 1.0 as HifiControls Column { id: stats From a4987e494a45b98518f88de211ebc508f6c383db Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Mon, 16 Jul 2018 23:59:06 +0300 Subject: [PATCH 137/276] update imports --- interface/resources/qml/InteractiveWindow.qml | 4 ++-- interface/resources/qml/hifi/AvatarApp.qml | 4 ++-- interface/resources/qml/hifi/avatarapp/AdjustWearables.qml | 5 ++--- interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml | 2 +- interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml | 2 +- .../qml/hifi/avatarapp/AvatarWearablesIndicator.qml | 4 ++-- interface/resources/qml/hifi/avatarapp/BlueButton.qml | 4 ++-- .../resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml | 4 ++-- interface/resources/qml/hifi/avatarapp/InputField.qml | 4 ++-- interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml | 4 ++-- interface/resources/qml/hifi/avatarapp/MessageBox.qml | 4 ++-- interface/resources/qml/hifi/avatarapp/Settings.qml | 4 ++-- interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml | 2 +- interface/resources/qml/hifi/avatarapp/ShadowImage.qml | 2 +- interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml | 2 +- interface/resources/qml/hifi/avatarapp/SquareLabel.qml | 4 ++-- interface/resources/qml/hifi/avatarapp/Vector3.qml | 4 ++-- interface/resources/qml/hifi/avatarapp/WhiteButton.qml | 4 ++-- .../commerce/marketplaceItemTester/MarketplaceItemTester.qml | 4 ++-- interface/resources/qml/hifi/tablet/EditEntityList.qml | 4 ++-- interface/resources/qml/hifi/tablet/EditToolsTabView.qml | 4 ++-- 21 files changed, 37 insertions(+), 38 deletions(-) diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index e8ddbf823d..c217238e93 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -12,9 +12,9 @@ import QtQuick 2.3 import "windows" as Windows import "controls" -import "controls-uit" as Controls +import controlsUit 1.0 as Controls import "styles" -import "styles-uit" +import stylesUit 1.0 Windows.Window { id: root; diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index aea5931627..39590748cf 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtQml.Models 2.1 import QtGraphicalEffects 1.0 -import "../controls-uit" as HifiControls -import "../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 import "avatarapp" Rectangle { diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index 5fff14e4a1..0740914440 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -1,9 +1,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Layouts 1.3 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit -import "../../controls" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit Rectangle { id: root; diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml index 9d9db010fb..d3c9cd1d5f 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml @@ -1,6 +1,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../styles-uit" +import stylesUit 1.0 ShadowRectangle { id: header diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml index f66c7121cb..36cb4b1080 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 import QtQuick.Window 2.2 -import "../../styles-uit" +import stylesUit 1.0 QtObject { readonly property QtObject colors: QtObject { diff --git a/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml index cb73e9fe71..8b28d4c66b 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml @@ -1,6 +1,6 @@ import QtQuick 2.9 -import "../../controls-uit" -import "../../styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 ShadowRectangle { property int wearablesCount: 0 diff --git a/interface/resources/qml/hifi/avatarapp/BlueButton.qml b/interface/resources/qml/hifi/avatarapp/BlueButton.qml index e668951517..0cc84d5ba0 100644 --- a/interface/resources/qml/hifi/avatarapp/BlueButton.qml +++ b/interface/resources/qml/hifi/avatarapp/BlueButton.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit HifiControlsUit.Button { HifiConstants { diff --git a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml index 1387c0791a..780981a5a3 100644 --- a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml +++ b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml @@ -1,7 +1,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/avatarapp/InputField.qml b/interface/resources/qml/hifi/avatarapp/InputField.qml index 905518ef0f..2020d56c96 100644 --- a/interface/resources/qml/hifi/avatarapp/InputField.qml +++ b/interface/resources/qml/hifi/avatarapp/InputField.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit TextField { id: textField diff --git a/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml index 4b868b47ce..6c2101498c 100644 --- a/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml +++ b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml @@ -1,5 +1,5 @@ -import "../../controls-uit" as HifiControlsUit -import "../../styles-uit" +import controlsUit 1.0 as HifiControlsUit +import stylesUit 1.0 import QtQuick 2.0 import QtQuick.Controls 2.2 diff --git a/interface/resources/qml/hifi/avatarapp/MessageBox.qml b/interface/resources/qml/hifi/avatarapp/MessageBox.qml index f111303214..eb28745b1a 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBox.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBox.qml @@ -1,7 +1,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index 71bfbb084d..38acce2b2c 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -2,8 +2,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml index c2d84bb371..a2c84fad47 100644 --- a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml +++ b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml @@ -1,4 +1,4 @@ -import "../../styles-uit" +import stylesUit 1.0 import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml index 3995446e49..51e1043702 100644 --- a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml +++ b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml @@ -1,4 +1,4 @@ -import "../../styles-uit" +import stylesUit 1.0 import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml index 741fce3d8d..3968fcb1ff 100644 --- a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml +++ b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml @@ -1,4 +1,4 @@ -import "../../styles-uit" +import stylesUit 1.0 import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml index e2c456ec04..69aff47373 100644 --- a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml +++ b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml @@ -1,5 +1,5 @@ -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/Vector3.qml b/interface/resources/qml/hifi/avatarapp/Vector3.qml index d77665f992..698123104f 100644 --- a/interface/resources/qml/hifi/avatarapp/Vector3.qml +++ b/interface/resources/qml/hifi/avatarapp/Vector3.qml @@ -1,7 +1,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit import "../../controls" as HifiControls Row { diff --git a/interface/resources/qml/hifi/avatarapp/WhiteButton.qml b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml index dc729ae097..d0a4a152db 100644 --- a/interface/resources/qml/hifi/avatarapp/WhiteButton.qml +++ b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 -import "../../styles-uit" -import "../../controls-uit" as HifiControlsUit +import stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit HifiControlsUit.Button { HifiConstants { diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index c3d87ca2f5..ee9858103c 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -17,8 +17,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.0 import QtQuick.Layouts 1.1 import Hifi 1.0 as Hifi -import "../../../styles-uit" as HifiStylesUit -import "../../../controls-uit" as HifiControlsUit +import stylesUit 1.0 as HifiStylesUit +import controlsUit 1.0 as HifiControlsUit diff --git a/interface/resources/qml/hifi/tablet/EditEntityList.qml b/interface/resources/qml/hifi/tablet/EditEntityList.qml index d484885103..d2fb99ea0a 100644 --- a/interface/resources/qml/hifi/tablet/EditEntityList.qml +++ b/interface/resources/qml/hifi/tablet/EditEntityList.qml @@ -4,8 +4,8 @@ import QtWebChannel 1.0 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 WebView { diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml index 00084b8ca9..76078b4afd 100644 --- a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml @@ -4,8 +4,8 @@ import QtWebChannel 1.0 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 -import "../../controls-uit" as HifiControls -import "../../styles-uit" +import controlsUit 1.0 as HifiControls +import stylesUit 1.0 TabBar { id: editTabView From 8b922ad7ccb857188f2c5a74bf881d551d7d8434 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 18 Oct 2018 11:07:45 -0700 Subject: [PATCH 138/276] Add missing dependency --- assignment-client/src/Agent.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 5f1e1ca74a..0b590a6d27 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -57,6 +57,7 @@ #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" #include "AgentScriptingInterface.h" +#include "ResourceRequestObserver.h" static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; @@ -99,6 +100,8 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); + // Needed to ensure the creation of the DebugDraw instance on the main thread DebugDraw::getInstance(); From 64f67d5eb7f2c10337696acd274b8d5bf42a0a56 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Thu, 18 Oct 2018 22:03:50 +0300 Subject: [PATCH 139/276] update imports for utilities / all other qml files found --- interface/resources/QtWebEngine/UIDelegates/Menu.qml | 4 ++-- interface/resources/QtWebEngine/UIDelegates/MenuItem.qml | 4 ++-- interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml | 4 ++-- scripts/developer/utilities/audio/TabletStats.qml | 3 +-- scripts/developer/utilities/lib/jet/qml/TaskList.qml | 4 ++-- scripts/developer/utilities/lib/jet/qml/TaskListView.qml | 4 ++-- scripts/developer/utilities/lib/plotperf/Color.qml | 4 ++-- scripts/developer/utilities/render/antialiasing.qml | 4 ++-- .../developer/utilities/render/configSlider/ConfigSlider.qml | 4 ++-- .../developer/utilities/render/configSlider/RichSlider.qml | 4 ++-- scripts/developer/utilities/render/deferredLighting.qml | 4 ++-- scripts/developer/utilities/render/engineInspector.qml | 4 ++-- scripts/developer/utilities/render/highlight.qml | 4 ++-- .../developer/utilities/render/highlight/HighlightStyle.qml | 4 ++-- scripts/developer/utilities/render/lod.qml | 4 ++-- scripts/developer/utilities/render/shadow.qml | 4 ++-- scripts/developer/utilities/render/transition.qml | 4 ++-- scripts/developer/utilities/workload/workloadInspector.qml | 4 ++-- .../marketplace/spectator-camera/SpectatorCamera.qml | 4 ++-- 19 files changed, 37 insertions(+), 38 deletions(-) diff --git a/interface/resources/QtWebEngine/UIDelegates/Menu.qml b/interface/resources/QtWebEngine/UIDelegates/Menu.qml index 46c00e758e..adfd29df9e 100644 --- a/interface/resources/QtWebEngine/UIDelegates/Menu.qml +++ b/interface/resources/QtWebEngine/UIDelegates/Menu.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 -import "../../qml/controls-uit" -import "../../qml/styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: menu diff --git a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml index 6014b6834b..b4d3ca4bb2 100644 --- a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml +++ b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 -import "../../qml/controls-uit" -import "../../qml/styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 Item { id: root diff --git a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml index e4ab3037ef..089c745571 100644 --- a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml +++ b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 -import "../../qml/controls-uit" -import "../../qml/styles-uit" +import controlsUit 1.0 +import stylesUit 1.0 import "../../qml/dialogs" QtObject { diff --git a/scripts/developer/utilities/audio/TabletStats.qml b/scripts/developer/utilities/audio/TabletStats.qml index 2f8d212a2a..b50acabec4 100644 --- a/scripts/developer/utilities/audio/TabletStats.qml +++ b/scripts/developer/utilities/audio/TabletStats.qml @@ -11,8 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 - -import "qrc:////qml//styles-uit" +import stylesUit 1.0 Item { id: dialog diff --git a/scripts/developer/utilities/lib/jet/qml/TaskList.qml b/scripts/developer/utilities/lib/jet/qml/TaskList.qml index 5b1aa0afb5..166f604666 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskList.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskList.qml @@ -12,8 +12,8 @@ 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 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../jet.js" as Jet diff --git a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml index 2c75865698..0f083aa72c 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml @@ -12,8 +12,8 @@ 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 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../jet.js" as Jet diff --git a/scripts/developer/utilities/lib/plotperf/Color.qml b/scripts/developer/utilities/lib/plotperf/Color.qml index 15d7f9fcc9..1ad72fe2e6 100644 --- a/scripts/developer/utilities/lib/plotperf/Color.qml +++ b/scripts/developer/utilities/lib/plotperf/Color.qml @@ -12,8 +12,8 @@ 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 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { diff --git a/scripts/developer/utilities/render/antialiasing.qml b/scripts/developer/utilities/render/antialiasing.qml index 1a8f9dac2d..5abfd30935 100644 --- a/scripts/developer/utilities/render/antialiasing.qml +++ b/scripts/developer/utilities/render/antialiasing.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" import "../lib/plotperf" diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index 41de77fb09..bf9089d82c 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -12,8 +12,8 @@ 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 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml index 01b14f3d48..ff16cb32ad 100644 --- a/scripts/developer/utilities/render/configSlider/RichSlider.qml +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -12,8 +12,8 @@ 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 +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index a9479b2935..64e00acdac 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" import "../lib/jet/qml" as Jet diff --git a/scripts/developer/utilities/render/engineInspector.qml b/scripts/developer/utilities/render/engineInspector.qml index 1b9941e64e..16dd8eb985 100644 --- a/scripts/developer/utilities/render/engineInspector.qml +++ b/scripts/developer/utilities/render/engineInspector.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../lib/jet/qml" as Jet diff --git a/scripts/developer/utilities/render/highlight.qml b/scripts/developer/utilities/render/highlight.qml index 88d6a807ae..d8af2a828e 100644 --- a/scripts/developer/utilities/render/highlight.qml +++ b/scripts/developer/utilities/render/highlight.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" import "../lib/plotperf" import "highlight" diff --git a/scripts/developer/utilities/render/highlight/HighlightStyle.qml b/scripts/developer/utilities/render/highlight/HighlightStyle.qml index 371b7e81f7..475aadfdce 100644 --- a/scripts/developer/utilities/render/highlight/HighlightStyle.qml +++ b/scripts/developer/utilities/render/highlight/HighlightStyle.qml @@ -12,8 +12,8 @@ import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 import "../configSlider" import "../../lib/plotperf" -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls Item { id: root diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 889d8db836..892b43d8be 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../lib/plotperf" import "configSlider" diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index 464fe00eb9..a1d6777a68 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" diff --git a/scripts/developer/utilities/render/transition.qml b/scripts/developer/utilities/render/transition.qml index f74468a273..c150c523f9 100644 --- a/scripts/developer/utilities/render/transition.qml +++ b/scripts/developer/utilities/render/transition.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 import QtQuick.Dialogs 1.0 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "configSlider" import "../lib/plotperf" diff --git a/scripts/developer/utilities/workload/workloadInspector.qml b/scripts/developer/utilities/workload/workloadInspector.qml index 2eaa9d8133..746a572f29 100644 --- a/scripts/developer/utilities/workload/workloadInspector.qml +++ b/scripts/developer/utilities/workload/workloadInspector.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls import "../render/configSlider" import "../lib/jet/qml" as Jet import "../lib/plotperf" diff --git a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml index 033039b87d..753771987a 100644 --- a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml +++ b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml @@ -16,8 +16,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import "qrc:////qml//styles-uit" as HifiStylesUit -import "qrc:////qml//controls-uit" as HifiControlsUit +import stylesUit 1.0 as HifiStylesUit +import controlsUit 1.0 as HifiControlsUit import "qrc:////qml//controls" as HifiControls import "qrc:////qml//hifi" as Hifi From 77b6389671b8cdb22a4bf62c54e620bde83b9da4 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 18 Oct 2018 12:38:18 -0700 Subject: [PATCH 140/276] Correct location of dependency --- assignment-client/src/Agent.cpp | 3 --- assignment-client/src/AssignmentClient.cpp | 13 ++++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 0b590a6d27..5f1e1ca74a 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -57,7 +57,6 @@ #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" #include "AgentScriptingInterface.h" -#include "ResourceRequestObserver.h" static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; @@ -100,8 +99,6 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); - // Needed to ensure the creation of the DebugDraw instance on the main thread DebugDraw::getInstance(); diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 426f3ce6fc..06b3f4da86 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -35,6 +35,8 @@ #include "AssignmentClientLogging.h" #include "AssignmentFactory.h" +#include "ResourceRequestObserver.h" + const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; @@ -49,6 +51,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); auto addressManager = DependencyManager::set(); @@ -159,7 +162,7 @@ void AssignmentClient::setUpStatusToMonitor() { void AssignmentClient::sendStatusPacketToACM() { // tell the assignment client monitor what this assignment client is doing (if anything) auto nodeList = DependencyManager::get(); - + quint8 assignmentType = Assignment::Type::AllTypes; if (_currentAssignment) { @@ -170,7 +173,7 @@ void AssignmentClient::sendStatusPacketToACM() { statusPacket->write(_childAssignmentUUID.toRfc4122()); statusPacket->writePrimitive(assignmentType); - + nodeList->sendPacket(std::move(statusPacket), _assignmentClientMonitorSocket); } @@ -256,10 +259,10 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer message) { const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); - + if (senderSockAddr.getAddress() == QHostAddress::LocalHost || senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) { - + qCDebug(assignment_client) << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode."; QCoreApplication::quit(); } else { @@ -312,6 +315,6 @@ void AssignmentClient::assignmentCompleted() { nodeList->setOwnerType(NodeType::Unassigned); nodeList->reset(); nodeList->resetNodeInterestSet(); - + _isAssigned = false; } From 11add70ef2b31986eb089eef38fa39c7d417e8d4 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 18 Oct 2018 14:58:51 -0700 Subject: [PATCH 141/276] style tweaks --- scripts/system/html/css/edit-style.css | 109 +++++++++++---------- scripts/system/html/js/entityProperties.js | 6 +- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 30dc959ef3..b50b5233f5 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -473,7 +473,7 @@ input[type=checkbox]:checked + label:hover { #properties-list fieldset { position: relative; /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ - margin: 21px -21px 0 -21px; + margin: 0 -21px 21px -21px; padding: 0.1px 21px 0 21px; border: none; border-top: 1px rgb(90,90,90) solid; @@ -500,11 +500,6 @@ input[type=checkbox]:checked + label:hover { box-shadow: none; } -#properties-list > fieldset#properties-header { - margin-top: 0; - padding-bottom: 0; -} - #properties-list > fieldset > legend { position: relative; display: table; @@ -523,7 +518,7 @@ input[type=checkbox]:checked + label:hover { box-shadow: 0 -1px 0 rgb(37,37,37), 0 4px 4px 0 rgba(0,0,0,0.75); } -div.section-header, .sub-section-header, hr { +div.section-header, hr { display: table; width: 100%; margin: 21px -21px 0 -21px; @@ -536,17 +531,6 @@ div.section-header, .sub-section-header, hr { outline: none; } - - -.column .sub-section-header { - background-image: none; - padding-top: 0; -} - -.sub-section-header, .no-collapse, hr { - background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; -} - div.section-header:first-child { margin-top: -2px; padding-top: 0; @@ -554,10 +538,6 @@ div.section-header:first-child { height: auto; } -.sub-section-header { - margin-bottom: -10px; -} - #properties-list > fieldset > legend span, .section-header span { font-family: HiFi-Glyphs; font-size: 30px; @@ -646,7 +626,7 @@ hr { } .property.range label{ - display: block; + padding-bottom: 3px; } .property.range input[type=number]{ margin-left: 0.8rem; @@ -956,12 +936,11 @@ div.refresh input[type="button"] { } #properties-list fieldset .two-column { - padding-top: 21px; + padding-top: 10px; display: flex; } -#properties-list .two-column fieldset { - /*display: table-cell;*/ +#properties-list .two-column fieldset { width: 50%; margin: 0; padding: 0; @@ -969,19 +948,27 @@ div.refresh input[type="button"] { box-shadow: none; } +#properties-list .two-column .column { + position: relative; + top: -10px; +} + #properties-list .two-column fieldset legend { - display: table; width: 100%; margin: 21px -21px 0 -21px; - padding: 0 0 0 21px; + padding: 6px 0 0 21px; font-family: Raleway-Regular; font-size: 12px; color: #afafaf; - height: 20px; + height: 10px; text-transform: uppercase; outline: none; } +#properties-list .two-column + .property { + margin-top: 6px; +} + fieldset .checkbox-sub-props { margin-top: 0; } @@ -1321,16 +1308,11 @@ th#entity-hasScript { border-right: 1px solid #575757; } - -#no-entities { - display: none; - position: absolute; - top: 80px; - padding: 12px; - font-family: FiraSans-SemiBold; - font-size: 15px; - font-style: italic; - color: #afafaf; +#properties-base { + border-top: none !important; + box-shadow: none !important; + margin-bottom: 5px !important; + top: -15px; } #properties-base #property-type-icon { @@ -1351,14 +1333,23 @@ th#entity-hasScript { #properties-base #div-property-locked { position: absolute; - top: 0px; + top: 6px; right: 140px; + display: table-cell; + vertical-align: middle; } #properties-base #div-property-visible { position: absolute; - top: 20px; + top: 26px; right: 20px; + display: table-cell; + vertical-align: middle; +} + +#properties-base #div-property-locked label, +#properties-base #div-property-visible label { + background-position-y: 1px; } #properties-base .checkbox label span { @@ -1367,18 +1358,13 @@ th#entity-hasScript { padding-right: 6px; vertical-align: top; position: relative; - top: -2px; + top: -4px; } #properties-base input[type=checkbox]:checked + label span { color: #ffffff; } -#properties-base + hr { - margin-top: 12px; -} - - #id label { width: 24px; } @@ -1409,22 +1395,26 @@ input#property-scale-button-reset { z-index: 99; position: absolute; width: 96%; - padding-left:1%; - margin-top:5px; - margin-bottom:10px; + padding-left: 1%; + margin-top: 5px; + margin-bottom: 10px; background-color: #2e2e2e; } #property-userData-saved, #property-materialData-saved { - margin-top:5px; - font-size:16px; - display:none; + margin-top: 5px; + font-size: 16px; + display: none; } +#div-property-serverScripts-status label { + position: relative; + top: 8px; +} #property-serverScripts-status { position: relative; - top: -3px; + top: 5px; right: -20px; } @@ -1439,6 +1429,17 @@ input#property-scale-button-reset { flex-direction: column; } +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; +} + input[type=button]#export { height: 38px; width: 180px; diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 060ed1f67a..1a52cf939d 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -3024,13 +3024,15 @@ function loaded() { let serverScriptProperty = properties["serverScripts"]; let elServerScript = serverScriptProperty.elInput; let serverScriptElementID = serverScriptProperty.elementID; + let serverScriptStatusElementID = serverScriptElementID + "-status"; let elDiv = document.createElement('div'); elDiv.className = "property"; + elDiv.setAttribute("id", "div-" + serverScriptStatusElementID); let elLabel = document.createElement('label'); - elLabel.setAttribute("for", serverScriptElementID + "-status"); + elLabel.setAttribute("for", serverScriptStatusElementID); elLabel.innerText = "Server Script Status"; let elServerScriptStatus = document.createElement('span'); - elServerScriptStatus.setAttribute("id", serverScriptElementID + "-status"); + elServerScriptStatus.setAttribute("id", serverScriptStatusElementID); elDiv.appendChild(elLabel); elDiv.appendChild(elServerScriptStatus); elServerScript.parentNode.appendChild(elDiv); From 45a92fd53745bf961e028cbf6dd9342ca8c1485b Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 18 Oct 2018 15:12:38 -0700 Subject: [PATCH 142/276] style tweaks --- scripts/system/html/css/edit-style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index b50b5233f5..8205fcd340 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -956,7 +956,7 @@ div.refresh input[type="button"] { #properties-list .two-column fieldset legend { width: 100%; margin: 21px -21px 0 -21px; - padding: 6px 0 0 21px; + padding: 16px 0 0 21px; font-family: Raleway-Regular; font-size: 12px; color: #afafaf; From 9f91238945ed9f5e27116a7be8a334285b724f39 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 18 Oct 2018 15:19:29 -0700 Subject: [PATCH 143/276] UX/UI improvements --- .../marketplaceItemTester/ItemUnderTest.qml | 352 ++++++++++++ .../MarketplaceItemTester.qml | 499 +++++++----------- .../marketplaceItemTester/spinner.gif | Bin 46135 -> 59412 bytes 3 files changed, 555 insertions(+), 296 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml new file mode 100644 index 0000000000..4852158df9 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -0,0 +1,352 @@ +// +// ItemUnderTest +// qml/hifi/commerce/marketplaceItemTester +// +// Load items not in the marketplace for testing purposes +// +// Created by Kerry Ivan Kurian on 2018-10-18 +// 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 +// + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import Hifi 1.0 as Hifi +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit + +Rectangle { + id: root; + color: hifi.colors.baseGray + width: parent.width - 16 + height: childrenRect.height + itemHeaderContainer.anchors.topMargin + detailsContainer.anchors.topMargin + + property var detailsExpanded: false + + property var actions: { + "forward": function(resource, assetType, resourceObjectId){ + switch(assetType) { + case "application": + Commerce.openApp(resource); + break; + case "avatar": + MyAvatar.useFullAvatarURL(resource); + break; + case "content set": + urlHandler.handleUrl("hifi://localhost/0,0,0"); + Commerce.replaceContentSet(toUrl(resource), ""); + break; + case "entity": + case "wearable": + rezEntity(resource, assetType, resourceObjectId); + break; + default: + print("Marketplace item tester unsupported assetType " + assetType); + } + }, + "trash": function(resource, assetType){ + if ("application" === assetType) { + Commerce.uninstallApp(resource); + } + sendToScript({ + method: "tester_deleteResourceObject", + objectId: resourceListModel.get(index).id}); + resourceListModel.remove(index); + } + } + + Item { + id: itemHeaderContainer + anchors.top: parent.top + anchors.topMargin: 8 + anchors.left: parent.left + anchors.leftMargin: 8 + width: parent.width - 16 + height: childrenRect.height + + Item { + id: itemNameContainer + width: parent.width * 0.5 + height: childrenRect.height + + HifiStylesUit.RalewaySemiBold { + id: resourceName + height: paintedHeight + width: parent.width + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + size: 14 + color: hifi.colors.white + wrapMode: Text.WrapAnywhere + } + + HifiStylesUit.RalewayRegular { + id: resourceUrl + anchors.top: resourceName.bottom; + anchors.topMargin: 4; + height: paintedHeight + width: parent.width + text: resource + size: 12 + color: hifi.colors.faintGray; + wrapMode: Text.WrapAnywhere + } + } + + HifiControlsUit.ComboBox { + id: comboBox + anchors.left: itemNameContainer.right + anchors.leftMargin: 4 + anchors.verticalCenter: itemNameContainer.verticalCenter + height: 30 + width: parent.width * 0.3 - anchors.leftMargin + + model: [ + "application", + "avatar", + "content set", + "entity", + "wearable", + "unknown" + ] + + currentIndex: (("entity or wearable" === assetType) ? + model.indexOf("unknown") : model.indexOf(assetType)) + + Component.onCompleted: { + onCurrentIndexChanged.connect(function() { + assetType = model[currentIndex]; + sendToScript({ + method: "tester_updateResourceObjectAssetType", + objectId: resourceListModel.get(index)["resourceObjectId"], + assetType: assetType }); + }); + } + } + + Button { + id: actionButton + property var glyphs: { + "application": hifi.glyphs.install, + "avatar": hifi.glyphs.avatar, + "content set": hifi.glyphs.globe, + "entity": hifi.glyphs.wand, + "trash": hifi.glyphs.trash, + "unknown": hifi.glyphs.circleSlash, + "wearable": hifi.glyphs.hat, + } + property int color: hifi.buttons.blue; + property int colorScheme: hifi.colorSchemes.dark; + anchors.left: comboBox.right + anchors.leftMargin: 4 + anchors.verticalCenter: itemNameContainer.verticalCenter + width: parent.width * 0.10 - anchors.leftMargin + height: width + enabled: comboBox.model[comboBox.currentIndex] !== "unknown" + + onClicked: { + root.actions["forward"](resource, comboBox.currentText, resourceObjectId); + } + + background: Rectangle { + radius: 4; + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!actionButton.enabled) { + hifi.buttons.disabledColorStart[actionButton.colorScheme] + } else if (actionButton.pressed) { + hifi.buttons.pressedColor[actionButton.color] + } else if (actionButton.hovered) { + hifi.buttons.hoveredColor[actionButton.color] + } else { + hifi.buttons.colorStart[actionButton.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!actionButton.enabled) { + hifi.buttons.disabledColorFinish[actionButton.colorScheme] + } else if (actionButton.pressed) { + hifi.buttons.pressedColor[actionButton.color] + } else if (actionButton.hovered) { + hifi.buttons.hoveredColor[actionButton.color] + } else { + hifi.buttons.colorFinish[actionButton.color] + } + } + } + } + } + + contentItem: Item { + HifiStylesUit.HiFiGlyphs { + id: rezIcon; + text: actionButton.glyphs[comboBox.model[comboBox.currentIndex]]; + anchors.fill: parent + size: 30; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + color: enabled ? hifi.buttons.textColor[actionButton.color] + : hifi.buttons.disabledTextColor[actionButton.colorScheme] + } + } + } + + Button { + id: trashButton + property int color: hifi.buttons.red; + property int colorScheme: hifi.colorSchemes.dark; + anchors.left: actionButton.right + anchors.verticalCenter: itemNameContainer.verticalCenter + anchors.leftMargin: 4 + width: parent.width * 0.10 - anchors.leftMargin + height: width + + onClicked: { + root.actions["trash"](resource, comboBox.currentText, resourceObjectId); + } + + background: Rectangle { + radius: 4; + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!trashButton.enabled) { + hifi.buttons.disabledColorStart[trashButton.colorScheme] + } else if (trashButton.pressed) { + hifi.buttons.pressedColor[trashButton.color] + } else if (trashButton.hovered) { + hifi.buttons.hoveredColor[trashButton.color] + } else { + hifi.buttons.colorStart[trashButton.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!trashButton.enabled) { + hifi.buttons.disabledColorFinish[trashButton.colorScheme] + } else if (trashButton.pressed) { + hifi.buttons.pressedColor[trashButton.color] + } else if (trashButton.hovered) { + hifi.buttons.hoveredColor[trashButton.color] + } else { + hifi.buttons.colorFinish[trashButton.color] + } + } + } + } + } + + contentItem: Item { + HifiStylesUit.HiFiGlyphs { + id: trashIcon; + text: hifi.glyphs.trash + anchors.fill: parent + size: 22; + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: enabled ? hifi.buttons.textColor[trashButton.color] + : hifi.buttons.disabledTextColor[trashButton.colorScheme] + } + } + } + } + + Item { + id: detailsContainer + + width: parent.width - 16 + height: root.detailsExpanded ? 300 : 26 + anchors.top: itemHeaderContainer.bottom + anchors.topMargin: 12 + anchors.left: parent.left + anchors.leftMargin: 8 + + HifiStylesUit.HiFiGlyphs { + id: detailsToggle + anchors.left: parent.left + anchors.leftMargin: -4 + anchors.top: parent.top + anchors.topMargin: -2 + width: 22 + text: root.detailsExpanded ? hifi.glyphs.minimize : hifi.glyphs.maximize + color: hifi.colors.white + size: 22 + MouseArea { + anchors.fill: parent + onClicked: root.detailsExpanded = !root.detailsExpanded + } + } + + ScrollView { + id: detailsTextContainer + anchors.top: parent.top + anchors.left: detailsToggle.right + anchors.leftMargin: 4 + anchors.right: parent.right + height: detailsContainer.height - (root.detailsExpanded ? (copyToClipboardButton.height + copyToClipboardButton.anchors.topMargin) : 0) + clip: true + + TextArea { + id: detailsText + readOnly: true + color: hifi.colors.white + text: { + if (root.detailsExpanded) { + return resourceAccessEventText + } else { + return (resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." + } + } + font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) + wrapMode: TextEdit.NoWrap + + background: Rectangle { + anchors.fill: parent; + color: hifi.colors.baseGrayShadow; + border.width: 0; + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (root.detailsExpanded) { + detailsText.selectAll(); + } else { + root.detailsExpanded = true; + } + } + } + } + + HifiControlsUit.Button { + id: copyToClipboardButton; + visible: root.detailsExpanded + color: hifi.buttons.noneBorderlessWhite + colorScheme: hifi.colorSchemes.dark + + anchors.top: detailsTextContainer.bottom + anchors.topMargin: 8 + anchors.right: parent.right + width: 150 + height: 30 + text: "Copy to Clipboard" + + onClicked: { + Window.copyToClipboard(detailsText.text); + } + } + } +} diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index f06612d035..89b1dd3915 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -11,14 +11,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Dialogs 1.0 +import QtQuick 2.10 import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 import Hifi 1.0 as Hifi -import "../../../styles-uit" as HifiStylesUit -import "../../../controls-uit" as HifiControlsUit +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit @@ -29,20 +27,208 @@ Rectangle { property string resourceAccessEventText property var nextResourceObjectId: 0 property var startDate - signal sendToScript(var message) HifiStylesUit.HifiConstants { id: hifi } ListModel { id: resourceListModel } - color: hifi.colors.white + color: hifi.colors.darkGray - AnimatedImage { - id: spinner; - source: "spinner.gif" - width: 74; - height: width; - anchors.verticalCenter: parent.verticalCenter; - anchors.horizontalCenter: parent.horizontalCenter; + Component.onCompleted: startDate = new Date() + + // + // TITLE BAR START + // + Item { + id: titleBarContainer + // Size + width: root.width + height: 50 + // Anchors + anchors.left: parent.left + anchors.top: parent.top + + // Title bar text + HifiStylesUit.RalewaySemiBold { + id: titleBarText + text: "Marketplace Item Tester" + // Text size + size: 24 + // Anchors + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 16 + width: paintedWidth + // Style + color: hifi.colors.lightGrayText + // Alignment + horizontalAlignment: Text.AlignHLeft + verticalAlignment: Text.AlignVCenter + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 1 + } + } + // + // TITLE BAR END + // + + Rectangle { + id: spinner + z: 999 + anchors.top: titleBarContainer.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: buttonContainer.top + color: hifi.colors.darkGray + + AnimatedImage { + source: "spinner.gif" + width: 74 + height: width + anchors.centerIn: parent + } + } + + Rectangle { + id: instructionsContainer + z: 998 + color: hifi.colors.darkGray + visible: resourceListModel.count === 0 && !spinner.visible + anchors.top: titleBarContainer.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 20 + anchors.right: parent.right + anchors.rightMargin: 20 + anchors.bottom: buttonContainer.top + anchors.bottomMargin: 20 + + HifiStylesUit.RalewayRegular { + text: "Use Marketplace Item Tester to test out your items before submitting them to the Marketplace." + + "\n\nUse one of the buttons below to load your item." + // Text size + size: 20 + // Anchors + anchors.fill: parent + // Style + color: hifi.colors.lightGrayText + wrapMode: Text.Wrap + // Alignment + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + ListView { + id: itemList + visible: !instructionsContainer.visible + anchors.top: titleBarContainer.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: buttonContainer.top + anchors.bottomMargin: 20 + ScrollBar.vertical: ScrollBar { + visible: !instructionsContainer.visible + policy: ScrollBar.AlwaysOn + parent: itemList.parent + anchors.top: itemList.top + anchors.right: itemList.right + anchors.bottom: itemList.bottom + width: 16 + } + clip: true + model: resourceListModel + spacing: 8 + + delegate: ItemUnderTest { + } + } + + Item { + id: buttonContainer + + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + height: 40 + + property string currentAction + property var actions: { + "Load File": function() { + buttonContainer.currentAction = "load file"; + Window.browseChanged.connect(onResourceSelected); + Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)"); + }, + "Load URL": function() { + buttonContainer.currentAction = "load url"; + Window.promptTextChanged.connect(onResourceSelected); + Window.promptAsync("Please enter a URL", ""); + } + } + + function onResourceSelected(resource) { + // It is possible that we received the present signal + // from something other than our browserAsync window. + // Alas, there is nothing we can do about that so charge + // ahead as though we are sure the present signal is one + // we expect. + print("!!!! resource selected"); + switch(currentAction) { + case "load file": + Window.browseChanged.disconnect(onResourceSelected); + break + case "load url": + Window.promptTextChanged.disconnect(onResourceSelected); + break; + } + if (resource) { + print("!!!! building resource object"); + var resourceObj = buildResourceObj(resource); + print("!!!! installing resource object"); + installResourceObj(resourceObj); + print("!!!! notifying script of resource object"); + sendToScript({ + method: 'tester_newResourceObject', + resourceObject: resourceObj + }); + } + } + + HifiControlsUit.Button { + enabled: !spinner.visible + anchors.right: parent.horizontalCenter + anchors.rightMargin: width/4 + anchors.verticalCenter: parent.verticalCenter + color: hifi.buttons.blue + fontSize: 20 + text: "Load File" + width: parent.width / 3 + height: parent.height + onClicked: buttonContainer.actions[text]() + } + + HifiControlsUit.Button { + enabled: !spinner.visible + anchors.left: parent.horizontalCenter + anchors.leftMargin: width/4 + anchors.verticalCenter: parent.verticalCenter + color: hifi.buttons.blue + fontSize: 20 + text: "Load URL" + width: parent.width / 3 + height: parent.height + onClicked: buttonContainer.actions[text]() + } } function fromScript(message) { @@ -117,285 +303,6 @@ Rectangle { itemType: entityType, itemId: resourceObjectId }); } - - Component.onCompleted: startDate = new Date() - - ColumnLayout { - id: rootColumn - spacing: 30 - - HifiStylesUit.RalewayRegular { - id: rootHeader - text: "Marketplace Item Tester" - height: 40 - width: paintedWidth - size: 22 - color: hifi.colors.black - anchors.top: parent.top - anchors.topMargin: 20 - anchors.left: parent.left - anchors.leftMargin: 12 - } - - Rectangle { - height: root.height - 100 - width: root.width - anchors.left: parent.left - - ScrollView { - id: scrollView - anchors.fill: parent - anchors.rightMargin: 12 - anchors.bottom: parent.top - anchors.bottomMargin: 20 - anchors.leftMargin: 12 - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn - - frameVisible: false - - contentItem: ListView { - spacing: 20 - height: 200 - model: resourceListModel - interactive: false - - delegate: Column { - spacing: 8 - - RowLayout { - id: listRow - width: scrollView.width - 20 - anchors.rightMargin: scrollView.rightMargin - spacing: 5 - - property var actions: { - "forward": function(resource, assetType, resourceObjectId){ - switch(assetType) { - case "application": - Commerce.openApp(resource); - break; - case "avatar": - MyAvatar.useFullAvatarURL(resource); - break; - case "content set": - urlHandler.handleUrl("hifi://localhost/0,0,0"); - Commerce.replaceContentSet(toUrl(resource), ""); - break; - case "entity": - case "wearable": - rezEntity(resource, assetType, resourceObjectId); - break; - default: - print("Marketplace item tester unsupported assetType " + assetType); - } - }, - "trash": function(resource, assetType){ - if ("application" === assetType) { - Commerce.uninstallApp(resource); - } - sendToScript({ - method: "tester_deleteResourceObject", - objectId: resourceListModel.get(index).id}); - resourceListModel.remove(index); - } - } - - Column { - Layout.preferredWidth: scrollView.width * .6 - spacing: 5 - Text { - width: listRow.width * .6 - text: { - var match = resource.match(/\/([^/]*)$/); - return match ? match[1] : resource; - } - font.pointSize: 12 - horizontalAlignment: Text.AlignBottom - wrapMode: Text.WrapAnywhere - } - Text { - width: listRow.width * .6 - text: resource - font.pointSize: 8 - horizontalAlignment: Text.AlignBottom - wrapMode: Text.WrapAnywhere - } - } - - ComboBox { - id: comboBox - - Layout.preferredWidth: listRow.width * .2 - - model: [ - "application", - "avatar", - "content set", - "entity", - "wearable", - "unknown" - ] - - currentIndex: (("entity or wearable" === assetType) ? - model.indexOf("unknown") : model.indexOf(assetType)) - - Component.onCompleted: { - onCurrentIndexChanged.connect(function() { - assetType = model[currentIndex]; - sendToScript({ - method: "tester_updateResourceObjectAssetType", - objectId: resourceListModel.get(index)["resourceObjectId"], - assetType: assetType }); - }); - } - } - - Repeater { - model: [ "forward", "trash" ] - - HifiStylesUit.HiFiGlyphs { - property var glyphs: { - "application": hifi.glyphs.install, - "avatar": hifi.glyphs.avatar, - "content set": hifi.glyphs.globe, - "entity": hifi.glyphs.wand, - "trash": hifi.glyphs.trash, - "unknown": hifi.glyphs.circleSlash, - "wearable": hifi.glyphs.hat, - } - text: (("trash" === modelData) ? - glyphs.trash : - glyphs[comboBox.model[comboBox.currentIndex]]) - size: ("trash" === modelData) ? 22 : 30 - color: hifi.colors.black - horizontalAlignment: Text.AlignHCenter - MouseArea { - anchors.fill: parent - onClicked: { - listRow.actions[modelData](resource, comboBox.currentText, resourceObjectId); - } - } - } - } - } - - Rectangle { - id: detailsContainer - - width: scrollView.width - 20 - height: resourceDetails.isOpen ? 300 : 20 - anchors.left: parent.left - - HifiStylesUit.HiFiGlyphs { - id: detailsToggle - anchors.top: parent.top - text: resourceDetails.isOpen ? hifi.glyphs.minimize : hifi.glyphs.maximize - color: hifi.colors.black - size: 22 - verticalAlignment: Text.AlignBottom - MouseArea { - anchors.fill: parent - onClicked: resourceDetails.isOpen = !resourceDetails.isOpen - } - } - - TextArea { - id: resourceDetails - - property var isOpen: false - - width: detailsContainer.width - 20 - height: detailsContainer.height - anchors.top: parent.top - anchors.left: detailsToggle.left - anchors.leftMargin: 20 - verticalScrollBarPolicy: isOpen ? Qt.ScrollBarAsNeeded : Qt.ScrollBarAlwaysOff - frameVisible: isOpen - readOnly: true - - text: { - if (isOpen) { - return resourceAccessEventText - } else { - return (resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." - } - } - font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) - wrapMode: TextEdit.NoWrap - } - } - - Rectangle { - width: listRow.width - height: 1 - color: hifi.colors.black - } - } - } - } - } - - Row { - id: rootActions - spacing: 20 - - anchors.left: parent.left - anchors.leftMargin: root.width / 6 - 10 - anchors.bottomMargin: 40 - anchors.bottom: parent.bottom - - property string currentAction - property var actions: { - "Load File": function(){ - rootActions.currentAction = "load file"; - Window.browseChanged.connect(onResourceSelected); - Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)"); - }, - "Load URL": function(){ - rootActions.currentAction = "load url"; - Window.promptTextChanged.connect(onResourceSelected); - Window.promptAsync("Please enter a URL", ""); - } - } - - function onResourceSelected(resource) { - // It is possible that we received the present signal - // from something other than our browserAsync window. - // Alas, there is nothing we can do about that so charge - // ahead as though we are sure the present signal is one - // we expect. - print("!!!! resource selected"); - switch(currentAction) { - case "load file": - Window.browseChanged.disconnect(onResourceSelected); - break - case "load url": - Window.promptTextChanged.disconnect(onResourceSelected); - break; - } - if (resource) { - print("!!!! building resource object"); - var resourceObj = buildResourceObj(resource); - print("!!!! installing resource object"); - installResourceObj(resourceObj); - print("!!!! notifying script of resource object"); - sendToScript({ - method: 'tester_newResourceObject', - resourceObject: resourceObj }); - } - } - - Repeater { - model: [ "Load File", "Load URL" ] - HifiControlsUit.Button { - color: hifi.buttons.blue - fontSize: 20 - text: modelData - width: root.width / 3 - height: 40 - onClicked: rootActions.actions[text]() - } - } - } - } + + signal sendToScript(var message) } diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif index 00f75ae62fa59b28a7813ca00c4185390e3e51da..0536bd1884f1cb4a814b729803b1996c8347d182 100644 GIT binary patch literal 59412 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�)#8TsS*rVAU%J0tpt=B2=IG*djp3b$!Y|O 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 L8@}NiK8F7Q4CAa& literal 46135 zcmdSCc|6p6|Nk%Rn1*JUu`e_BJ%lJa%x1_kZ^oL+9-)RWuCAV*-rBWm4Gav{ty^bg zWMpDuVrpt?W@ct?Zf;>=v1!w$@4oxaa#>c(BK{%LvdCmIl}fd-v9YtWb8v8=)9Fr5 zP7DUa)z#I_&CT82-NVDf)6>(-%WLb_t=qP3^Y-@k^YaS|3JMJk4G#~Gii(PfiP^h% zZ(Lkle0==Aefts<6Zh}mf8fA@b&(9qEE@bI;3*KXXnF)}hTIy!pu=FN$TiCedB-MMq;?%lgT{q)n%KmYvT!Gnhn zA5Kk8O;1nH%*;G~{P@X}Cr_U~efI3x?Ck9G=g(ifc=2*szbxz5WxZO~>({U6mh~H# z=I7@Z78c$t>mUF4$J=GSU)Jw`)rY_8BbOGxuK)U96yd*CJ5U(TBr7__P*0z46%P*& z;T=B@9}f?Y&e9LZ(vJxbVv*;(upF(ms_P&^T+3^)mDQ7hR<xJ+pLcX3pSknE*B1 zvES);jmMyX2@`&xI+cn&Ke295` zH-rdP9pwLtLWO=aHsXiOU)AzKBugw63W%E# z&OldKGvot@Af_@};0SNl_aAGnWtrFN+mW645k-nj8-Hua2=&G}kTOYtbo8*sLQbz= zPZM>-8zBx-Abnogj(o`qTI-2P<2#DK9!Y}MnVLaFxrE!{=oua>_)_tf3?X+)NK{lhtwUVVa-je<=>z!mY+sWrJi zV}YA-Ro~4JiqAZh(N9ruqeOb18@v39>2@N&P5FX-qotsBDvP)O;;VipQK~WajV*!k z@K_j;U*Vi{NFfcSUT(>MEBO_yDV-pjT z_3PK0F3Zf!Y{RlPE(-wO!omUo-pXnT?PM~ULZMKXWo>OuqtR?^Y;0|9H*el-XJ=<` zZ|~sX;OOWGEz8;2nZaPVxVX5xyL)FMbi85vnwS=rgyxw*NA z4jn2eSR#EL~isDL$O4|`kIRIX6@^k zdX1mErRGUw+xij1kFBnUYN)Pl`(xFgi58!heUnnafOcgZD%|kyWvks7wO4v71cSAX zOwTJk6L&e>&dG*@<2|YQ$m2~aA!V`h-61r?>AUGQ)D4yQ@vNG%yOPaEn5RA@&mUa8 z@Lj9pnDYnW`EB$H{vi7(tF6A?sx}tBcy*hXkd$JKrQucbrDik(muR8fW2bC~8Bl7n zQrWc}Mi&bBci;_(uVKm8doM*H1N!92~ z@BLOFZ^i8!{!&5`76G0#ikR24I=nqh^w#i3y9Q#_40(cLOzS$iis#)eeV zN4h9TABvSNturclqSLvvEyv-*Bs@Y=Brv5Bxdqp<+D7cET!a!ZPxfKOT(6AxxE0> zk(OU(^ncWEtEw*XdI{3YmRoa~&cJEI9$SA|2Fo&B)(TPs0bZM#nQh#-5jb#5OG_eg z$#27SrY#FbGdG<9pkW{G=H}+%;o;@w1-QIp$BvMYkkHUjK;)>XsJ(ml0ub-tzdt!S z`QX8Wsi~;|!I{g-`a{{-**Q5mfXKjvA31WQxVRV)`Pi{zu-|60+11t6u+y%uuje+~ zEiEmu**<;xbVo-==d#Y8J$vrlx%11qaN)wmixizxr6^aU7yMTkY7nQvhYLkyBIP~zSWDU8j5q03%@7=|cMNz4mC;>g zaP-q+MWlr)m1fVLiRQ@@+FgPEy-l^j123+)K@&|a5!YJ2cQfyuc*SeO(DEAbP0Q}E zw!2>z_u{b(yQuf5y$vpsujuiBXmQ}-zO)ctly+9rJ%`hUott$oV$WM{Wg(0y6gn$4 zMmOT_;d)5_=AoxgGz+{gsw!NyVho&kDE7qsWvaQ-`YF;yqr(kZ4Xd}@B*YVJHGc02 z)J|V~{P5W&!V6aL@Wx&ulleZ~FkK?<$8br=U|up?>4N$X-MYhml?;ZKhrBFvX$^?8yYkEs5VA=NRmZF*-)6|B32po+dzTX??}PUJUJB zy^mb`#E73 zbZAmHU9T{yAjOpt+XU&mW%l(*?IVlX>I~MfZSVn>m=t$Q6{M3%*>FIZy_@%~n+I>5 ztmVZAX7N(a`WP2vZ@q`*l|N7Xs81`8c~?kHcp)>=qj;G;aZSZ5b%!oy-?XfkLAHN# zimRqq+rW7~2hwKEV=C+cxxF7EGF2+^zfJW6MxT;Ge-+W(Hu~Q*&?^M{X&BLf(jW{0 zlm=l43~FGejh1D+Ea0bML~q!zWTwrRwQ185sI9E5KqLaJGn|S*5ai_K;_3PJv{PD-p z(NW-@Z{NOs=gysYyM9b1KOfUEG}G%5 z;eSugc3uthU2lSkc`cPPTJIP)(p76TS&5~e-c&KF?&*?J&kmCR9cxp2>K^ZT{M9oJ zrNw$%eS$ad4a$b}wg?mU`K^|>E@8;uG#++RGThXm71?+-b3%5Pk#R?jpfzNwmt{N@ zQsd~9IV?&zKh#&-u1mKWd2I`MREP^^^Xxe<*mU@`)OQ}5Gh#P0>peqRm!!g?Ms=Gr z-0uB0KNvcLbL-T&K*B}k1XmI-Y&Yx3I2G-1IwBEcmbb)aWO`x1-vxpg3 z)!__b-FC$+gs~G-MMHsX1I2a(p$$~g3u(E6W>(BKrV=$&==%kYd?m_tLXBlBHjU4V z=m0Slts_Y)5P4AD&uAYez?vD0rKG4@zi*d-to#vU>UQ2^7#T-npFrytmEvxK+-RXY zw3+Tw?`P83c8$v9xMRiQ13QH6N%mG(sauPUm8hxBudwLDfgAyb)@>hEa(8aQxWs`F z{#UcZn(_xlO0=5KR8K$7t|GTD@*!j%jOix zL)YpzD|k%Ql4P;SARBqc6+s+=nmd}I+Ucr+QprZO3(4<3N3h1{&x_6UN$qHU-7mNI zn*CQCw3L+8iiqRiPdR{KHp3>GJMLIA(ti!Dxji)qJw8V@oPMldzaDne+=RBawuT+F z-LmYL1!8OnTFN(ETwGQp*;}@3fs<@*T7$R)m}@}m$jHd(=;)Z3nAq6Z`1tt5#Kfee zq_niO%*;%XWW!-L9Am>tc1cMIFw`s-3vjx=z8=KYAmV6iYwPIfICJI<$f|)~266S3 zD_7v41GdTBi8Y*cz&07qI)F_Edc3U96Kjxrd=1R7U_x_4`t92#%M9arg+Pk^FL>nt z2$26O#3IiR-{PQudCwT4D{k2Hl&6WTJxz5^9pSgKJY8qcZ+Ye%YdUgD&w{)QDW9D* zsNiNHJKRn>*x=04=rxTmp0Cn2HLMJgm+51;q#TV5mDi3+XPtVmSc1R$rml3WMMX>S z=39YXKwZDkh}Ds4~A~G zH)RrV}8ofsi>6BjSv<&0h*SyMI=n9%R1A-5ncE<*8@zudQ$B!B0J zr?Uq~6h~iLV+f=>gepoHVj)>e$Tc;MZfE2^iXxU0Cdn*R?LeBCrXfRFj>-$Tdi20P7~O~#M{AYl%aHP> zrmd8bGyP1#Fhhpug3r4=BMQnT^C&+{gMscZcqd|DfvQa}`eUae(yCQ9)Z0vFhf| ztx=tK#SM0AtbXZguK)YEnzh#noMg^^O0L{Q{FWo)KM89L%f2jq-m4@h=10SgXcZt@ zhUP#lOD9S&g)*RTPXc2P{!eKE1_x`h5 zkvN068St0eDz9A@96B2s8p1vq4xIsZxpNFFD=QL-1R~}y=F5(bOW6hNmO;Mkx-1Z7 zczP}&7|av5Z{P0YJ8uoQHFxH&B?Eh8f%J3IT(p`}0r z_RPh_OZ+V@Ej@PZSY>79@#Dwg8Ded1EdcR}6Q6R7Q>RX~wY7n{;h8gM&YwSj>Cz<_ z!C+hnG78uv!-K+m_wIo~AsjSMPfx>xLJ&;=anA~W{qRd`eylIQu(Z?TXv6He z5F1z3^zS!$6>kxnjFt|XwPorgeS!Z_$fBfO#%|8`DUwf%k{-Ye)D{ELfvtD z?gi6pd!Q-#!{4=pK{sw1_EkH_c{WUGv&hF6{r}N0tO-fSsXx}VC7#_(p>)Qw8%lyI zn|wp#-x3?A*z75n+F=wSzhHW++)59jl0)o;K~isyX8ITZ^mF|Wq8hQt^W(Re<=H}} z#z@8b$oF#D{@T-2``yj+rk1DcuCKN_$CEM>$=0=WSc;p)dy9xS;!RRHwfv*?j)^?B zP4nIwYV^~@8K@z|%2K@}JF%><=$4anf0L>wU*Q3iT4L z=1vP$CoUJJ&+XuP&SFl5?Pb<+Kr`t-9n_YDn z2HnXrs)qeU_VF7-B||+o4df_RaWYdkDM7ZCF|QHVjUi3RXBruA3f$@JgIZ!ctr)c6 z{F6zG=){6-Uj??b#zIGL^3!8kBe7_EXJZjaK^)UI4Mjw(V2Ve>` z$Q4(PxSe>bhDGRF7$Kk_DGjtFd0`bsniPi4PJcr4JtAb^%(_n^qdNJscr`J7vibbV z7#(uyDrM?>5!~P*$w@1Ys-{pUzadC0(q#5X+b;{fVc?FDS?tp^ZBZHD1 zca{v=cHBYoQYYiDtqgej2csFTWWeJ-*fN920-)K;%ZuA111^V#hK7ZO?ON9E-MhoX zmz;8BWF$;#(9qboZy!7o{JfF@27raj0;vUD!GlX0Ft34q2D}DSL3ktx%6Ba-Ex<>E zc_7@s0|P+VP51XN!5U=GSFc{Zwk&x1H$Fao`}S?_{sxGj;nbPiS;GM{KsGn5fuRPe z#TV(k!dAnL4miGm_5UG)|3-*Ko=e|C=QC3s2uIoV30Mza+!CFAQu8gBqYQ&?{qC7a zcB84;E~NbIqz&H3Lbfp8u%q#&^u7dD3gTLxP`&%H3$1=TwXEzmv}cEHs6OJh`#^t_ zYEw{1S!Z}!F(lzsTGVFo{p|>{R@wS3jpk6Kvi}S58ihoIz6;Z$8nqFg{zb&2)vc=T zhs_T?&2dCEZ`n{7u_5K~_U!Jb-nysT3Y?{m8@k3%Mmh95t;?9Dbruwf*B{eE%8t!v zo;Y;$n3$P;o8wJ2CyTpEo8}!UKKkh=-Er+Mhgq8279qQhQBIC@`Se4jLCyUmCc82s zO%))Bl{VnPd)IYt)4iD*bZKJ2D|8A`dWyaNhHzgtsSuSml*f-;@FSfR4k*rH zp}lDRz*)@qQ4LN_;&XKuI+<3#1gI1cpknErwXdP^YMI zd;eZD42HPMr=Bc>oEfS_hiNipk~YoT#;+fzKP5S%#wXY)-N@s5`FbjLyVlo2&xjGI z@vbAslvGe_NUK(DZa^%UV#S#3kbG>qy;)n;3Eqm72D-zD^$E+uLgVR^_a9C&gv5ED zUk`E+j-c)Dpze`$H5`GEE&V#h7baZrVY;0WIC*s0p7*ydhGc(~H91PtfM~-l};Q(mi`UR*vgRV1Z#&Op# z;EG&(`==ovz%4gxSJa)aU0d?Tqobo^V@n;2iHRkL42sSxx^i&M;`7)TCN9V?R!s7M zAAa*@so4ydn?Lu!Fl%AN!h%`*H~QT4!ulIt{;vtK$kX@@U^ZLy5R%F@GUDbCO+YYv{CtL+UqSRQ`PrX$E+!tu?AxjQ4{GP2Za>h)xG$(yMufwZ4x z#?#9^+=PAOyE{V}3Ps&c&;7cae`0GadQpBmvkTI5wOVX)ib>7aO_XxkV7!{6LWyH{ z=x_ADIpAV@?j&Me_T(w@cK7g0P}8G!l`YOyHn>rja@j2f)8lU`T0+_^ExoIIh+0K? zNi~b6Pi;zN)Jla6ZOAmGqbX@4;-p5YfV5oR z2p_DvD8-kdVD6-x0p*ycW3jxs{dH^>6lNMVaG0X^$O;NFomYU?51SI9 zNYhkb=ma({8xl1kr$Yr;n=B~Rv?BvLh&}HQWt%cHA+x_=`D^DKnSC!tC@c6t+kb`v?d zK%49;<>aU;RQ?9a4=%QE$jqHGVR~nS7D1{Jod}W#5Dne+I+CeN30=L90!{ zKa!chTJK~wO1r?{X86+81~rBJo|EK6@*waRQ1H(pol>{hT=_jmiWE^KaG8T=KX45; zOt6(UqKvEb@V2z~ucm6|v{{}!_d48uT|%>-O02&@YO31V7jswj2XubBX0-Qf>=qId zl9iQ(Q;&a**Fc|>nmd8k(fQM0!<#E`%mEMkU}VGonX)Wy7Yzy;U&J?9TiL#SyRWY= z$e3aO47cZEVq)OYUP{VRguz|P$jJChhYl{#<>loaI&=tbY7`X}!D}n9X9lVB7n>R< zPyQu!?(FR3?m1t+ykwX`T@JLJ`}+F8<_g@HyLRnU^ZCage;gV4b0B!<&K)?D=H6U+ z`gAFc1|jq>zx?vKXNE%!ILqKpF+c*nB7XiGxBSlvhWOv;KMirjB2U*hu-oFz6bEs* zY(o-0%okVqsLqrY7_`)|vzOhj1no2%m8jM=3q{J?-W0%*&E*AqeoJX^t|y{YT}*=p zZk}++!dKXaJ7Jo=D>eo9;mXTegR;h2nH&jbM|j%){F{Z?f>!OybFamR#B~efM>NbZ zu33$T%ly4(k^`Mu)4Fu}+TzCtP8_N&+f>`NL%r8KA!XCnDG%ksdY!3}CaKUhdG-D# z?Qyey4627+)QtR`omRjD4l6;@2fnypq06{!?IVaVCCri8A|be+gDdHIU2O}3C!GW9{> zx=E$Y>y=HD(E{0|Ty)ydp(6=T(ff&M?T^&SFDFMrNsaCj#^l;bx&-DI4K&nd%&4Zz z(uT%0$X$~oa^wn0B{lNv$$UK7rrj@}BtJEhNm^*DmLs=~qf1Hl>#N0rH!-^DYp>nD zDnzP%OBcJ9mWjwh&scG!3A?PYNoyL%y6Ef8T_+?j-!V!_6csjQC=m7&1(8+8W&{kL zd?`uG?bK}yXBTFaEuG+*vS-joY|@H@APH`S^x47`zP4eiQKmC3Q(Bj_Jt8iQ%ruWq z)BR~v4S^AoesN@rLq$II;A16n4#w>~eIWIvQfl@A^3PXxUEKJ|PR`Z#< zE5fUMiPaC4%IRvqjvA%+tnMOOql}}82Bb?b+Iqxfzw_TRsB$!PZb+lPd2U$eY{J*3 zhj=_5<~6Ku2RAU+aA`wB;|t?LZdk)n2r$~*Jvul6SrJQv#s*kTv9`9}ym_;|{nC`s z+1c6E)fIG`0i!|oyko~FXASBXv9YlU2?^ZmCEQR3eP)1TxYEoWXTV%8FE2lS{5YG< z1`|N;Rg?Dib};w@HMvWdE`icAs4Ii6^6>C5+?2a{^Cl?E-MV!PHqk%-yc9NrwG$9I z1O9Sn7B61B0PuzRyCSLp;$|^7g<*X@5d4~n{u`byF8;@n`kxkJktg{ZkiBu6ZH={q zjK1$k2a9jem6lii)F>i($!;T@=i6 z2Udt~6UkvbB`G{q{`V*v>(rIIvG}XSLBbnst&M{>OV$e*td(5A|DKGHGKod!wKFq+ zlu_F_s3g45R+!tOReo(%-zz#6yJes0tklj-S8*>r3M!yKbwZB4-ZlAx53)AvqS_X=-WRjtR)DmmR(LB{Q-1H+_N&if_B~xUL zfD==49j~%1#HW{P4TWDv;GrzjX)3gT-rX7+Hs&Zm$F9G%hIX6wQXswS2;oFgGaBP4 z;jR-yLX-tDiUKO1(dF#k+@?W0OgpTh==la~XpbqA20gg`lmHztO|^yW<~0Pe*YsI3 zkrp>BnXAzT5Q8Pb+pR=CgTYZVw-h2EwcjRCNV z^wh~@G0NPk65Fh-BS@x=KLt`pPl>D6VN{=!i==1_(W19e)~iitEbGWDF=MjMRjnZB z0&IY7NB-aTKtL4)j<(^2Ls!>7XOCd19C&n)tiU#X*REY~ z2?Q?TfsrHFIZR#_?ApPnAuH?CcL8|xf`X+z7w%>ryzm0H5aE8v3bWqW*a&y^PA{vo z^V5EaT~?I#xbtrC zy?_NLF|a@+1E(@8x+U`eH-i0Er-N<6oS?NK1p_;_tWOwq< zNE}sZm#Z)v+1;tbP%iJDI9;=pl*AN=RR4BHZ&ZJr`2K?LM1P5;_V(y4Fb=EQ4G zjkB07*pn1&MTo|%J?bRMJ^IDtfaiy(-UMG|e!u7W0dg~Bkx^?;icbukEO5F2;z%4b zEF8mh(y7jb8zB=Ko3j{>QYS3)3Y#Cb>(Y~hP9rqmpE`WE&XU52xRb{=@?1=7POEMZ zxh4Kg%*ATBzCn!gyIq3Xu^{}3tn7Ag6yyZS-%gz&Ls2w-Ds;HP#Rk$RlTitz^ZHxe ze^#L~_N_5h*eNGkW4*IRHMIhECht!Cr z4CO6ij6F!D*rJJ?I&7L3Nk*(_IIkHSokco;rCiH9i+?(Tr?B4m8sax_Xwa#9gPWlg zQ?d+RfaVdBkRHQ zA38-~aot5?E;bC*)TLCLaB{5`#y}VA!r!J?I`)*V>t&5mqz7X>tuMTCWhzP7IFqv< z?3%xpL?N((C=E6GOzB@EJA_Y@(+Z(z6EoQ{<=0_RRQqt9fM)x^aFskvd}?31{kEE8 zkR#SphLW^?7t>z;Zr~rQ;oNR}8QorAv)rE-_Wnm&)WC#;Yac6c{l#G#Z>1 z-+i|fc!Pr<-0O#Ma}Rdoa9bLb^j4Ilxf^;b7Q#V28ie0q6&%DUpqrPOxkO`-oBYjg z3kXd>E2O%*8r}xy9xDR&0`LNV4#tYGKfiHfsi6n25W-Q&idAs#gc}aIxet19uZ4e( zS(vT>s&C)Eg?|Ba753+F+6^;y1wa1=p`Yu&h3fx;zA@q67-={!bXacv-uKutY~dqS z6DR-WUGW^XK(zEuvj|DNhVemUZ6n`xUlJ}9RwPaVV)(!W4Q#$*k9WZX=KFQHY>+Mdhb-^wMOv&bOBblBlMn5Yz&BY9=9E-ZK(I|&LJLU96K^t5<6w5Mc z-+a$-o=>H6U)kLvjoF?NL+6XAaf0#GZkEwGLl-SwRBZ+sLd|4&grfS$3OTjNfF)ltN1ruxs#0@k+TKf6$$VK-0ozZFbbSCVPkJZir$ri?9FZq&Wh)z# z+9v4soBcEE=D0s5h*C4-rw!iHG<2DT={Lj*F!N^RIr;2_WulAJXg=I~4m`k6Yj z3}m9DswuHSDb6h99`>QCV3DMwr}fYtZTgEfKK!a{m0EGfQ|o9L~B| zEWB`Y9aKcV*t}j5bgyXj!DpFP3@?FTw8K+OZp#eo^B9I3^W3Cg0sGI1{zd&qDUMj= z>G=lVZBg7_Gx%eiga%fbXWejOjzK z!^@!$ttELA@{Dm}fmR~+lgO3;{TJHWm`Ftc8 z;%hMLu(CSHnl!q4>^NGLw93gZh4gv?jVIa6`KbzSVD{6`jt5<(?=v;(r+=5OTuqNP z<*?{a#u3-)F{aE)dIQ6ZBPSZ5fz|kq?aL;Sk!Cm%Ts(bsJ&JE6(Mp$WvBC?6FiLP{DEH$yu4!Mk6X`SIM(Y0f&SDJ?`Fi|8k3g$(?xt4JQJM9vGaA_o#2A(ryRNrsrFO}0HA@p=Dz7cCNE{$Ha1H}8r)X`$CaF& zow<#)xA#)M@p-rb+Ix|aO9%#+mEa{8a7HgFX({43c5_nG>+*AUqhG5SHZdI>X za%pY-bWjN#>j2}%ix)5c@WT(_EFMf-u;g;<)~%m@`f0^h;p4}DE)#N36S*M^gLQ>q z!UDDen0}F@u(;=oa2BzGnEz2${vQ!yktg_Dkge4&{^XjZVZd*rzu~#x7@SzY2(=u zHO3^;m}R4MjHM&`8!Eu>sdUyR6eZmw3Kc*i??+LHiCf#mD(Oj<0s4FT zapJqGMpEfD6~>X{hS)y3R***%G1|1RuZfA2p7W+_>F{b3v#vd4R-TpVpgEIpqbIBm zr=~Zx^oXWrpd39)b#zPbEikKlh+_RncXVteK>oic|@57Wwe&S){vk%$k?mg~h{5i#32uW$DV z7${jQNqsucG&iXGwM2VGBm0ZM{TqFrT*Foxlt;L85cmw!iW^MaBgqvfP(c*(we>|n zaPA{{D@rBY+vMOVD%fL$kD_uG8VrZ-LubAs|K%@PH5wQz-x zw8(+!QYAL0)kJh+WX40x3~p6v$cG^1H~dB_rkcyMFNhy(aIW9A?iDA| zHOLh z|GbF$7eq$dN&Y@$|04}K_k)OGTpL5-nq$|CyA9eWZ=!akxHKCJ?mU_oSvi)(e2vbj zKVWFv{?G`~lvf#Ks%tRot1atrxMo)BUT{&)_HwP@g*LNMOWZoMA_H8Fx)eEU(OKou zJmyD|!&SQkTuP8^lV|s%!oiH7HRCo=Cw|!2caoJEh09YQ+h7V}NZ0i}-5NXAbJWnn zM5Y`sl5>|HXBpK`mq>E*F+w2w1juxhOK%46j6N2#<{57{eS4LZy#y(-Du={G_hsb9 zppCOgwI#jj63(x}Q{puAh*-Sgwp1h(V-$zkO?*iRpqxXG4-{c5+EtE<$8{@~h#3Ms4U7h61PWF?p4qGvu~R7uLUyp+76a!+w0S7(UTmZ;)dZ&l;h7< zv)a=Z0(~iYmkWhr9T%=RRacL&h_2*vVx8*YaItGz6|}-kne4Qp-bnv#25$EDQPzQH z_IOnF*9KoUl@z625FS43|AJXx8WLAUIhMk!tgM!R8e)HTPaQe1hq3wirRf4`dwRI39c%54O`_ zTakNJ5k5l=pIU-jeIV2b4PBZT?%usr9RbTQU~?SY$2)LfDb#>>#^E>`PBFj%YB<6G zaWs5n2|nHd?eVIC!nNxa=at}5AiSFh+%>nahJ$FJFYb&3 z)_!8_A2+4{CWr=uJlIZuOaA@(=4*=FnGq|uJ2H(0c#hgfYg>&TAY;9uZI zTiRu`RkGKu%nod>*l0v!=VyE8&b+R@USXB*X+xZUBErP^Im*3{rR|=O_CgsnFFeV< z)@&#jtuw8(Q`CGn!Pk(~v#u_$($^eEsB`Ia^VT!}m~2#CRBtU4@S^&z=F!}^%8*I- ztSsj>*?PNX?V`>NXgK6}y_n*hA7EIPPc)VwU(=rvv={H-H$1{pOhS!M%hea@<<~DWXC*Lu;zVZ14fb*>|L27DR0Wb8*Q5xg7!=YjjE9&p88S90R}1* z7Ju9{x$xZC8gefBtv@;Om8mK@0KG6!v_@K;BXlz24t+HVLkJ{gqZj&0QE^sGg-|Vy zJXF(LIwC_>^-(8wax6It`xI&fA#?r^I>B8;AcMtU5XlFvO&to-;uGuCsJ>F}lwo?-&0si&~QOVJXi+`KWWr=~6+x1=jc+&g?Vz@W3;vbyG91fn=*zC z*G8uc*(&ST??TFZt~ACO zI7Wr+s{}9H-Z|(@sNW!ZWDeUOnB6tkj2k`}G2m%kxj5W)?%D8lbiY;8`{awNREklE z*sM)3A7mi2z0Jls`qvSWma3xTD6+6p6*U!Y+?OmPpvpk3GK_^}$s$UVlzenwUz+5C zJ{BYA>EBN$q`E^2u;hB#>RH zSS1@n|I8eFJ)Dfx8}?u|Gm*9&3`#Dc!2!t|@#Bf7ec$&Z#Z~ zUdFbBaW*n6R18`h-;wXKrd(^m7L{cZ*V?Cy`q-W+eJP-iPi=da6=$_=Zb61p$-^;W zmGr@gG^9Un($gi6L%Ah>Yr!Z-bgaEkAk3w)xW#pYf3~Y`WWxKGbT!UoQEFAL&x7-V z{&;GXn(Lh4nz7<=0X!QmY$L}&BZ`pr9c`J_TMD}~2}$>?gcMVCh?>W-ZcA=?M{@*u z)v7NaG6jYiM(o$bjb3i@nmU!*jzZ@q0TJ>&;z{`jJ!PzBH;eewH*e9>J><^#y zXD}EZ9v)k_uFRalZKa*df)~iYxIGSgW^nKhgwNorM{e$4R$l;dLAefoy#>C!R9^n) zwNma~#>U3KERDm_G*}%60rlClpI{6h{pVhQSrJx)o28#OvEl7;?z11O?*7lvCJ@>N0VKnHQn#JulRO@{!*

X`;XqU}D_;LS&aBz*~PA2qfpUy~;>cVYO(5hG>l>cfIQ3s6yRi+YE72u<@w zUdwm!iE@17sj}D6Sw9D}7HI88RJroVWPyF+{1c{*pG7-kIL9myNZ4&$uWW~ zpRb+Lyr6Qq&s!z4?(BAh&7&3{pbi>D;Wvd0wa@`6$>L!UHVCx|OM^rldLIXK2~)w2kB$VOgBg+E^dbGhz3t6y@@Gi_-s#7mUy2k&Oiyr9 zH3aV~A+T~r+9av0UFTL4Tw5;|Aw5l}{0y7uYy>_$-=|+LMc-H0Z!3J1N$6_{^O`kl zxU-CZInMZ^;ItVY2d;QzOJ84~o7%uRb6=E(Ei{-Eg2i)i3*>W-Q>mXI4w7dF2L}jR z!uRKA^0sYTx-JbG9NLLdRS8!m z>V?TcEn&#fGrO)ETKv!*=SfB>6`$OdSQdqFz=`=6<-d;)ZMiNHWNyG*Xt=hz#lk)D z=hWD=<6AJ>+n|D?;kHwIFH|*^AVm-jAydBPQSY89-I1io$AwJk{E~etMm%rsC$?rY zSrK6;@_xJz$ z3OlMhuc#+|!Pk8;Jrq6j(*dLfk)y(|=bx1ztfhj4l8j2|Ysp;%0ud`3FqtN35hR}> zxV2C&Q$X63qpBi?ML^XxrumZOnHgUcY2hK;Qc0B3pEvQn6+?NwsJMcIipq= zCn4`s;DR|si(0KXZV{cjPiB;z>YCw%owFfTRCT4eYG=+BX!+(vlO!Uf471cPoa4<_ zP0xk+Jt*N~PWn4;tA^Qci~5I{Ql|Z831oIvz|gU~feU2y_BP&()svzEy$L@I6hnr8NHc(w1{X^|Z$HD-1qc1X<{`W?4quE0x1zy}4^Elg+?KY+ z;rpZjygoilXYu^~m)HwlJ>0WrPh4Ca_bJlU)YOcO3^9DG_X&a6=WoNecWfFuQON0Xto8eQL!0@n9j~>Zc6i zGks#~U*E-Ap-hK_FofXktge0(u`e1&W;<>`a9<};&Fx5df&N)NG(Oo8Pc|c zDj^}7e|+_1-e1QqLM2AN0D052L%C_n0Bg;;w?@2F z>b;;T*?<)M!Q=*-kt45a?D}-UK{J*9TJeZ@{=okU9+tRClV3Ca+QpH7wROERwP%?=wyIw%?xH z*-v+*MNP+SP{Scnq)|eFt`TZHjlmHX;PVn2S7#zpUXce8q!z0JT~pLdw=%&+tcSiq zM;(&06X*)CK7;RK%1b;ed`zbr4UOWWeN_hYiIY`>)TX3CMu}9M83WnkQ-j!Tx3#gL znv@ovRwb4=*B4K+>KfH1yVnwBPFrw>rSrAji^zLrTzq?7Hs*HGNO>htt;@#2C!^YQ zgloEDs=n`#c9Di5LSf6>S2#>T2gha0ZNccYa{N$o(3$N8SDv`08?E}q&G??An_@|& zHU|}$$u89kJ@-)0e&hFDE&ffMRTSYn?RWu&TUo>baTW8JG{u%fJ65Zty})EhZ!b5n zR?JKanf&xR%RafU+{{x_Qd(i2zviFeX&?7yICsDS9xYsvXsp;sfl~~SB!gE9!6gqW zmCBtS!{ue}YyE)3@N!^a;9r`{;Nh#-*w}sh_Q8X_FP@)T`vhUo zpx~}2!$<76ukyq1R}Bsha=#!8pX3Mlg`bZFRSGzO0O2smgg?(3R&0aAS;LCX+zQ@u zH;%sru>Y+7H6;FD6Jn7k@mmmGsFgZkFQx5zA=k#)nn63f3q4qFvxgsJkiIPhOFpfB zUC8oo{#abZ!5^y)w*&C{sh_|9 zsh@{$wE}HB|1T(mBd8iW%^J&?3d*!?A`F*?8*L8F^gLAeFzR=o+-mRmjVgPX8L!gW zPH(SWnSa}Ta?_;g_MoRvA3Z~Ee|s%?tB+q9&UpcoV)Z#aK@I9A3!@##%|vTYR0Q60-Zq9`p_#7`-$3TKFvrgmh;Y$_O; zA&PRvJb1sQgO_U6&eReBGM&X zQzr!#(yv{30N$ciIr`!oHw^z-+q^=si`LZCgt@(92*~ZCx&8A}Y_a@&I2=HOH5Bl` zERFW(5D;V++;{V~Y}o?elnx9G1P7%-KLgw=h0jWZ>v`~VvPnswLg)1KrR#az56QwO zm%z(~;8N-5+k^0RQuxdVJmZ5O!~$nNu3TBlD7c>nhj0DE8zLDT|SNWzvE5*9^e z2^0}6q-b40B`jf+NkW1MQrQ9_f(nAgt;4E-SPcy-)=oviBGncY+L?j9^<_{PrMx$3-fc^^iY?ty40{%J>s-DcAO<-_qJ?$wj1IjjTc|wU0tL zR+7Xq#YioKGfyY~db$+en2i6RIK4+!I9OrvH0_Rkd#@XH>!H;xf>E8i)W}tV&j>A# zbE?<=mr?pATeG@|{n&JlxL_t{)qyXFCGbK;;IQ839z))1eSN6E5CgO0_z1_XH=p0# zJ1G$;dh`^gPbNAZUH8%-GnG!DM2a=~4@`GWunklB>84WJ{?lG!K9IL(*S(NK#0fr- zzDN3(kWwP~ZU}o1yFbL%$EY`C%^u0Iko7(jIe?JH&If{gCh~wpT7NFE(~`@al{SZG+&y#UkuikQ?a!HPMMmgei*qFvOcBL*RH;QbX#B zwYc6o6U~+7VTfcUnT zd8M0hSy%m=?P84s2t)8+hv?y>6=PN0cP&BKsdI+b z37JJL1_D~Yj=635g;Cwql%i2PR_8CJx*r9eD8nYhJ>=)y+G|e59_a!oa7@ZBb>xS- zZJdt;Gil3AW9vPxSm(#YeZ9b?`RLHM=p6I*3Z>_3G@XjL;Six@fd zRj69a1l68`$!{pR0(qL-+g}t)gFE0CE-cV-&}s+Hvw-!cP_ZevUI>O)pzZIM!?6nr z$Rb&JW@ZLNYE=g_bpIn{zwyWKkI@;tcgMv4w0l>KE=2$Bv;Zr@SE7@6d?SY7xF5Q_ zDK4Pqh~;m?cfFp`?T(pWnwc2H7%+=h<>_~DAK@ufhUCs)9}zIeWuZ;snWjxa@MiDE zosx1IF0wV=_D;na^%OzmHY~MJJv=D7Am+3vCVdUYq}e}ZJ~PRAc{nlK`m#E*LsQ6-oRS+WYlm#D zC1<}31|ATb4d+|G3;_^p>QBvujfMgp;{wZvY!scfohYsf<+C(ttldPr{C(`h4MrG! ziF)+M?ARJH?0e6?+st(=Np>iC!dQawr#G-n;I2d?6Mx?|tQ-SZBucydUXIRkhVY&> zd8=c>t$@8`NiKOG8P_YoUb;_WWz)z3p$r90WCrwCN0HNFPy@8WfqJExkz~Y=<#y~R z1&&DW_uFomYlcErW+>@G#yCyrnut5%;NO7HVk1q1M#5Qsb25cZ6zrY<`! z9d?{?v{i_Tu@vN1BGW`Bk*tetfI}W{l4o@s>48}$h@o^d{G}R&qXyfPButkBWb^4I zwDPA2r}Mng95bF6BXB+ZYj>V8rQZED063yB)I4MCTFbWWmgnbL%b%92tk* zv%WtUDfl_mviujW+gqvsNe;2hl@~eNeE}EQ&F{*ji1G{{@fEzXl<0 zt*VBAjyfyk}L!$t@Umm#N0;UYX zfdC$l2l8yy9W7PmdQtvFbx;dhaDhA>P_?P*xZ%Z%3qB52XEd~?1qs&Rh!)t-0WoY< zkH#A-k-#oJDA~Ozi=wKbfcMi+KY^cupl$-1GChA}Gqq8Qs@g?Q3z~LJ9v-?)Zh?RZQbRb-}npoO}aRT&1tEtf* zxv~kz8&-`CYmfCeWRI3J+Bc-wO*r$);w-)pJ?-eLYfo;?nD0JQ+bC|I*q9KOCf0E& zV~*U9WotMUro~k-pH16g;o;vh#Y+g(d(OuilGlb`s@I60f7cWwo8kZ z=;mkUN@?yCssg`d{bUc%=A3#q+p%x8!fY#LUg7Mxv4c-Jb9aVqP?tWZgV>785@NP( z*wyP`an4W1cF@QJ4sGprx1#3XjxpY7s%XoOvdH%^qR29U2Vs;{PC-NBBej8oDtIjS za!TUZ>2IjV?{bXQ-ST}6Nd+9?R0L7?w!+c%d-E?5`(1DXmoofH8X90WxTt$;+-X4E z+P%NUASKVJ$h>#ob~mj!VgNGg;n@?tpYTSP#9g@XDcpav)85)V$sA^0-MlIg6<|e~ zEAl}c7*IDM2xW?;Es2A03cAQ8gNe`mHH~laVPBfRb#_eL?5n;d+S@#M#>U2rEYfe2 ztN+nc<=LR=L$&x6a!fB;NCf5WH!ci7NtQ+7Ldc{C&b>hMv|vaa+(v}#9w60@iHU(W zxS-u|D9-}UaH%@b!qk9VE{8T9 zx~?&ru+${WT+mmqM6rZQtxnI#+=4TsdR))7{`_M{*pDy?-S zI4aX;m=sVf>0?}7-*KN5b9*;~p;wYHM*5^{l*%YPJo+`MvP+Vkf?a}L8eK_NYD5NrYlw128df$bQT zY^7e01WeSO`XT_A4{zdbQER227OELOqEFCdyIgGp{9A|2`)oVhIeNHcxUI8#z`e1X zs4JKlb)%2Tyg~fX7wQFQ*2Nnx>OuHlgoF=MoKH40r~F2+tBX&#h1fNFJfbxRww~-H ziH{($hrZZ!M0@AON)hJzqpPKQ$()}nF+i*CfYJ-Iykh6M`ts3gD-nVZbG+C<}`&MnnD|vaUhz%B& z9y#(VkO-}CL9|;YlQlOtpE~uzg$F`3RAdc)T0((DXh$53C4xpgFw_F#v+53v>Y6QN z$5WNML5O~V&QD&|JTRXzX0;P+&dcdUXg5h^dL8; z&29h2WaX%auXgat73a-))egc0FUPzd#TopuYUGN3{Kh$wUA;lVV9bya-%X}>j+vm- zH!={kZzb~1MuK*WFh4Z4I=z*p(*~zlh-Udwf1ZS zks{QKGOVWB^8ykvCqLWtn|Ro-tp9F)`oOfinOtMc2U#T0Ob;~}vq$X8ysfiTHkjx# zKs}J3-&`f;476q4&U1+umk!*|lySBnmaIS9wkJQZ@1~1$zaV#V1~KOCy!rV@nG-Yc zF{<^37Z&41-N|DP62y}ArvI! zlXeUG2N=ZyX+Eh?fGTCw9R3#W#9=ptqBb2qG)!_!(d=V5r${u&o;y6Xd7P>xLbO+d zhE`Or4_QZRQ~!-4^eYo**)9mL@!0LB=yO_8MG~d19tVLDx;5;OQt0c}e!?Md+(r~( z&+L$BQI1A_#$J9Tr+p}N)6qi#l(a6-Y`k@Uk6~0|q&P6H)?*Oia&V_-uan7oKf5Te zJyO8Qrwi2+W-3{7BuEx3rl+U290gVaDTRkjjt7Z{(K|(KP5;I#=-6r9^kXOlPO`JG zuL|AoH`UuqE14-Pf%U-IV zln~>FupHvr;D_b;^Dm_Dix(Hx2~;;?AWI5ZL=A>xz#?jB9R_pdg2r$7bPwUSXUlQuHkeIfeZi>mXHE!n7pV3Zy<*paulwpAk13dYsyL3lad&cU_ z_9U;7ADC<^DuJgU4cVSOt47NWSFWIEgi0O`2E-lY7L=!uzch6TiYrpED68@%@+~{| zHpnsgNpg|gOzw1#?^E$PTwd2y#J?aFnAuI$kL+&pn@%d?og%oIk4!WFI6dsqDkE$}M4eMDdm|5p15$77{1u z?ENqtjeV5Z7r@>I%cZa{tRq(f6xh69n6FPCE38>b(8-7Q1Ec1it$-xS;98q+7-1p; z>ES}~?AZdR)O<}(XKCh=3t97ihp?V!;==w!V_lO_n*h<)pm`smB|G=t5bbE{=sofX z;R?pxb{s=ud$YoTy+K}C_5Q)BngbL@ct@`PwWMwoqLnRXM6>DRSE(29-KgbfI7R~- zdt>`_4B{y=L^)f?jG+t;XZ1wJr{?IP;%(J5tZP{t!~@~+zolCE526)L>i7%=tw1OC z0o~l|@ifWR`qBM|CoN!URBMTBWZ31nd+ildk0*s)hON7EmK)v4JK<`!yQE!9`+mcB zENcg;=-B!D=1Uks*VX)-FGeeTXsl~9Stg%<&~lpknIA4&t8u2`P%gIMWR01eSnHcV&V(9hWbmP4KHx+HY4Lj z!-pzFt1fw|`bbpQyB3AULBs~TmQ=?9py^w%ni}c?0R@t_wl;9X>-8ZL+KZHuKY-{(_eL`QE@b(C zfA2VhODfdemV!89eVxkV;PM$d8txuVk7-wjVdX@GZbg&O?6{fN$D;89g-}rKTM6$d zU*0|*%H8=_cnI5}*jlG_T)C`yK!1{znR71oq)G6f(|2}W-lh{( zXEu^LWIyj06ufnU+j7HW#H$^(A#I=6PrivOvXR}DDWO$(f2#xBXOjUljkIgy zc8&a=JNk^FSWII@&vOiS=$x(1iP#lhsEN}hVOwLfy>>mgQ5@EB>zF+4_T_DDryGg6 zeA2hJD68vf{F4*zD03SSwD8mE;_iuRG+DIso6}Iz(q;)o)q+Nh*mU4NK z7a0{giAX36WBYLO!(z!2ewg=qqv0Um+0#a=KCsbLcSZP)i*3n|##u-t%2cf1H_Ta{ zK-0t^T(fLPD;Qgd(%#L8gmEz}wP3C)>=092s2x1$j-h?pZu9V6$I3CU{wp*fK9!JnF3!YUN8b%lR@I6q{r@64` zsBXYbN)bz!+PH2I7g*E7LR`{QiE+e9ct=&h@g<3bEM7sJJq#dV8#+Ir3Vp>7oGg5-o=k*o!-^Ww=J2Q z{OWhQt^4s?+#_%}T!m%-^ECU72XXHO%l7Z1UJ(6W6l;VQ7@==UsQUv%Y|u#p$|um6 z0AvKD)9K&{Bgnm>FH7jZ60D(yYVwkjlE4%V*gFlPH<(%kOHaXqJl@O8&VI374#gM2 zjW6&-qQCp51apk4(EaJB3zA9Q%L7AK-z%LnwLRu30G%RNOVyb&sx8 zt3Qq^T2cYD<8k$dfaSJ3Muy6KF^9S1{3ChqHm*;~j-LmeNt;-gFXyC9med9`ijQv2 zFB-UQo?wrv{DnDI+Oq1g#sdprIophzfhdvj$J;SO4Eo4sUO~qP z<{!gv71jEt3J)YTOcenqEZaf>BW4OewQ$-rbAOGfmaubBt z)Xz4_>-O~^$cjjR+A)0X6RkDRQB@j$iK(mobjU=5)h^k`J6G_1)rr8vzx|Tz`W6d?H+ zQGtZLliGZEl|RceaS#w=;hLrD1s8v-A1+uYbq^$mYQQAcVmikkAn5)WmPi@51zg;W zJheh+XD9qz44VL@j%MDRb|CqZFR)@GB6sN#n`>MWu!`sf#8V1>RJaEc5;jqWW^Ra$ z*C*EN8VBhiWaRR4ctx`L_|pi$o+DD^%DVG}cwZ*FhHAZf(htpL80n&2ne03oB~(I6 zMcBsmhKrf$mxZpocX*O5%G~SfXuS7S*R3%llx{_rC3G2{ z$*C`n9Z3v8RevyPTXy!IxjWA)g2LU{Ir908IO2q$6?;CY$!D2!PL2BI3p1C+ex^-( zeq`9P?p5y4VZnP6Q#x&)jzqdeX2Di`wf-4{?$E8^W5gLFLxdl8{l5KTs;Qyot@i%j V-}`%i@9+J+zxVh4-rv9A{u}xRWtji~ From 4d7bc3094aed9445a5830edfb850c22839d7b2e4 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 18 Oct 2018 16:14:53 -0700 Subject: [PATCH 144/276] Fix regression --- .../hifi/commerce/marketplaceItemTester/ItemUnderTest.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml index 4852158df9..dcb67f3f12 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -52,7 +52,7 @@ Rectangle { } sendToScript({ method: "tester_deleteResourceObject", - objectId: resourceListModel.get(index).id}); + objectId: resourceListModel.get(index).resourceObjectId}); resourceListModel.remove(index); } } @@ -83,7 +83,7 @@ Rectangle { color: hifi.colors.white wrapMode: Text.WrapAnywhere } - + HifiStylesUit.RalewayRegular { id: resourceUrl anchors.top: resourceName.bottom; @@ -311,7 +311,7 @@ Rectangle { } font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) wrapMode: TextEdit.NoWrap - + background: Rectangle { anchors.fill: parent; color: hifi.colors.baseGrayShadow; From d7d49ed84e66f9517b7adeaa9a1baf0fd7b71f2b Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 18 Oct 2018 16:25:26 -0700 Subject: [PATCH 145/276] Remove cruft --- .../marketplaceItemTester/MarketplaceItemTester.qml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 89b1dd3915..5f2268132c 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -77,7 +77,7 @@ Rectangle { // // TITLE BAR END // - + Rectangle { id: spinner z: 999 @@ -281,15 +281,6 @@ Rectangle { } } - function addAllInstalledAppsToList() { - var i, apps = Commerce.getInstalledApps().split(","), len = apps.length; - for(i = 0; i < len - 1; ++i) { - if (i in apps) { - resourceListModel.append(buildResourceObj(apps[i])); - } - } - } - function toUrl(resource) { var httpPattern = /^http/i; return httpPattern.test(resource) ? resource : "file:///" + resource; @@ -303,6 +294,6 @@ Rectangle { itemType: entityType, itemId: resourceObjectId }); } - + signal sendToScript(var message) } From b9a2f19d302f87b74a13d75f45a0285883914d3d Mon Sep 17 00:00:00 2001 From: amantley Date: Thu, 18 Oct 2018 16:39:50 -0700 Subject: [PATCH 146/276] made all the suggested corrections except the dropdown qml. --- .../resources/qml/hifi/avatarapp/Settings.qml | 36 +++++++++--- interface/src/avatar/MyAvatar.cpp | 55 ++++++++++--------- interface/src/avatar/MyAvatar.h | 9 +-- 3 files changed, 62 insertions(+), 38 deletions(-) diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index 8749079940..fd72d70106 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -354,14 +354,36 @@ Rectangle { // "Lock State" Checkbox HifiControlsUit.CheckBox { - id: lockSitStandStateCheckbox; - visible: activeTab == "nearbyTab"; - anchors.right: reloadNearbyContainer.left; - anchors.rightMargin: 20; - checked: settings.lockStateEnabled; - text: "lock"; - boxSize: 24; + id: lockSitStandStateCheckbox + visible: activeTab == "nearbyTab" + anchors.right: reloadNearbyContainer.left + anchors.rightMargin: 20 + checked: settings.lockStateEnabled + text: "lock" + boxSize: 24 } + + // sit stand combo box + HifiControlsUit.ComboBox { + id: boxy + //textRole: "text" + currentIndex: 2 + model: ListModel { + id: cbItems + ListElement { text: "Force Sitting"; color: "Yellow" } + ListElement { text: "Force Standing"; color: "Green" } + ListElement { text: "Auto Mode"; color: "Brown" } + ListElement { text: "Disable Recentering"; color: "Red" } + } + //displayText: "fred" + //label: cbItems.get(currentIndex).text + width: 200 + onCurrentIndexChanged: { + console.debug(cbItems.get(currentIndex).text + ", " + cbItems.get(currentIndex).color) + console.debug("line 2") + } + } + } ColumnLayout { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9fe91e44b9..2c9b83b636 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -464,20 +464,19 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { } } -void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { +void MyAvatar::updateSitStandState(float newHeightReading, float dt) { const float STANDING_HEIGHT_MULTIPLE = 1.2f; const float SITTING_HEIGHT_MULTIPLE = 0.833f; - const int SITTING_COUNT_THRESHOLD = 240; - const int STANDING_COUNT_THRESHOLD = 20; - const int SQUATTY_COUNT_THRESHOLD = 600; + const float SITTING_TIMEOUT = 4.0f; // 4 seconds + const float STANDING_TIMEOUT = 0.3333f; // 1/3 second const float SITTING_UPPER_BOUND = 1.52f; if (!getIsSitStandStateLocked() && !getIsAway() && qApp->isHMDMode()) { if (getIsInSittingState()) { if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { // if we recenter upwards then no longer in sitting state - _sitStandStateCount++; - if (_sitStandStateCount > STANDING_COUNT_THRESHOLD) { + _sitStandStateTimer += dt; + if (_sitStandStateTimer > STANDING_TIMEOUT) { _averageUserHeightSensorSpace = newHeightReading; _tippingPoint = newHeightReading; setIsInSittingState(false); @@ -485,8 +484,8 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { } else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { // if we are mis labelled as sitting but we are standing in the real world this will // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { _averageUserHeightSensorSpace = newHeightReading; _tippingPoint = newHeightReading; // here we stay in sit state but reset the average height @@ -499,14 +498,14 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { } else { // tipping point is average height when sitting. _tippingPoint = _averageUserHeightSensorSpace; - _sitStandStateCount = 0; + _sitStandStateTimer = 0.0f; } } } else { // in the standing state if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { - _sitStandStateCount++; - if (_sitStandStateCount > SITTING_COUNT_THRESHOLD) { + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { _averageUserHeightSensorSpace = newHeightReading; _tippingPoint = newHeightReading; setIsInSittingState(true); @@ -514,7 +513,7 @@ void MyAvatar::updateSitStandState(float newHeightReading, float angleHeadUp) { } else { // use the mode height for the tipping point when we are standing. _tippingPoint = getCurrentStandingHeight(); - _sitStandStateCount = 0; + _sitStandStateTimer = 0.0f; } } } else { @@ -530,6 +529,7 @@ void MyAvatar::update(float deltaTime) { const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders const float COSINE_THIRTY_DEGREES = 0.866f; + const float SQUATTY_TIMEOUT = 30.0f; // 30 seconds float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); @@ -575,19 +575,17 @@ void MyAvatar::update(float deltaTime) { } float angleSpine2 = glm::dot(upSpine2, glm::vec3(0.0f, 1.0f, 0.0f)); if (getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y < (headDefaultPositionAvatarSpace.y - SQUAT_THRESHOLD) && (angleSpine2 > COSINE_THIRTY_DEGREES)) { - _squatCount++; + _squatTimer += deltaTime; + if (_squatTimer > SQUATTY_TIMEOUT) { + _squatTimer = 0.0f; + _follow._squatDetected = true; + } } else { - _squatCount = 0; + _squatTimer = 0.0f; } - glm::vec3 headUp = newHeightReading.getRotation() * glm::vec3(0.0f, 1.0f, 0.0f); - if (glm::length(headUp) > 0.0f) { - headUp = glm::normalize(headUp); - } - float angleHeadUp = glm::dot(headUp, glm::vec3(0.0f, 1.0f, 0.0f)); - // put update sit stand state counts here - updateSitStandState(newHeightReading.getTranslation().y, angleHeadUp); + updateSitStandState(newHeightReading.getTranslation().y, deltaTime); if (_drawAverageFacingEnabled) { auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); @@ -3893,8 +3891,8 @@ void MyAvatar::setIsInWalkingState(bool isWalking) { } void MyAvatar::setIsInSittingState(bool isSitting) { - _sitStandStateCount = 0; - _squatCount = 0; + _sitStandStateTimer = 0.0f; + _squatTimer = 0.0f; // on reset height we need the count to be more than one in case the user sits and stands up quickly. _isInSittingState.set(isSitting); setResetMode(true); @@ -3909,7 +3907,8 @@ void MyAvatar::setIsInSittingState(bool isSitting) { void MyAvatar::setIsSitStandStateLocked(bool isLocked) { _lockSitStandState.set(isLocked); - _sitStandStateCount = 0; + _sitStandStateTimer = 0.0f; + _squatTimer = 0.0f; _averageUserHeightSensorSpace = _userHeight.get(); _tippingPoint = _userHeight.get(); if (!isLocked) { @@ -4155,7 +4154,7 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) cons return stepDetected; } -bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { +bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { const float CYLINDER_TOP = 0.1f; const float CYLINDER_BOTTOM = -1.5f; const float SITTING_BOTTOM = -0.02f; @@ -4179,8 +4178,7 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(MyAvatar& myAvatar, const gl // in the standing state returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); // finally check for squats in standing - if ((myAvatar._squatCount > SQUATTY_COUNT_THRESHOLD) && !myAvatar.getIsSitStandStateLocked()) { - myAvatar._squatCount = 0; + if (_squatDetected) { returnValue = true; } } @@ -4216,6 +4214,9 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat } if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); + if (_squatDetected) { + _squatDetected = false; + } } } else { if (!isActive(Rotation) && getForceActivateRotation()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d1ba0fd9cf..be8b5fa1b2 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1116,7 +1116,7 @@ public: float getSprintSpeed() const; void setSitStandStateChange(bool stateChanged); float getSitStandStateChange() const; - void updateSitStandState(float newHeightReading, float angleHeadUp); + void updateSitStandState(float newHeightReading, float dt); QVector getScriptUrls(); @@ -1744,7 +1744,7 @@ private: float getMaxTimeRemaining() const; void decrementTimeRemaining(float dt); bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; - bool shouldActivateVertical(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; + bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); @@ -1757,6 +1757,7 @@ private: void setForceActivateHorizontal(bool val); bool getToggleHipsFollowing() const; void setToggleHipsFollowing(bool followHead); + bool _squatDetected { false }; std::atomic _forceActivateRotation { false }; std::atomic _forceActivateVertical { false }; std::atomic _forceActivateHorizontal { false }; @@ -1843,8 +1844,8 @@ private: float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; ThreadSafeValueCache _isInSittingState { false }; - int _sitStandStateCount { 0 }; - int _squatCount { 0 }; + float _sitStandStateTimer { 0.0f }; + float _squatTimer { 0.0f }; float _tippingPoint { _userHeight.get() }; // load avatar scripts once when rig is ready From a4adf2c71678853b4edd3737177585b3e1a5a9ab Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 18 Oct 2018 17:39:24 -0700 Subject: [PATCH 147/276] Fix name of Particles to ParticleEffect for default properties in edit.js --- scripts/system/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 96d3d763b0..71b4b2c54d 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -419,7 +419,7 @@ const DEFAULT_ENTITY_PROPERTIES = { sourceUrl: "https://highfidelity.com/", dpi: 30, }, - Particles: { + ParticleEffect: { lifespan: 1.5, maxParticles: 10, textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", From 51d767ac2bd9989f67f18a53e029f6e6796985a2 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Thu, 18 Oct 2018 17:46:32 -0700 Subject: [PATCH 148/276] Reasonable defaults for position and rotation filtering for vive tracked joints. --- interface/resources/controllers/vive.json | 33 +++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 42be6f3f04..24b1587691 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -53,15 +53,32 @@ { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, { "from": "Vive.RightHand", "to": "Standard.RightHand" }, - - { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot" }, - { "from": "Vive.RightFoot", "to" : "Standard.RightFoot" }, - { "from": "Vive.Hips", "to" : "Standard.Hips" }, - { "from": "Vive.Spine2", "to" : "Standard.Spine2" }, - { "from": "Vive.Head", "to" : "Standard.Head" }, - { "from": "Vive.RightArm", "to" : "Standard.RightArm" }, - { "from": "Vive.LeftArm", "to" : "Standard.LeftArm" }, + + { + "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.RightFoot", "to" : "Standard.RightFoot", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.Hips", "to" : "Standard.Hips", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.Spine2", "to" : "Standard.Spine2", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.RightArm", "to" : "Standard.RightArm", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, + { + "from": "Vive.LeftArm", "to" : "Standard.LeftArm", + "filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}] + }, { "from": "Vive.TrackedObject00", "to" : "Standard.TrackedObject00" }, { "from": "Vive.TrackedObject01", "to" : "Standard.TrackedObject01" }, From bb5c042f16bc0dd86785861af429db54925a572d Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 18 Oct 2018 17:51:20 -0700 Subject: [PATCH 149/276] Blend network animation --- .../resources/avatar/network-animation.json | 140 ++++++++++++++++++ interface/src/avatar/AvatarManager.cpp | 37 +---- interface/src/avatar/MyAvatar.cpp | 17 --- interface/src/avatar/MyAvatar.h | 2 - libraries/animation/src/AnimClip.h | 2 + libraries/animation/src/Rig.cpp | 117 ++++++++------- libraries/animation/src/Rig.h | 23 ++- .../src/avatars-renderer/Avatar.cpp | 24 +-- .../src/avatars-renderer/Avatar.h | 21 --- .../controllers/controllerModules/teleport.js | 2 - 10 files changed, 241 insertions(+), 144 deletions(-) create mode 100644 interface/resources/avatar/network-animation.json diff --git a/interface/resources/avatar/network-animation.json b/interface/resources/avatar/network-animation.json new file mode 100644 index 0000000000..0ba4ea465c --- /dev/null +++ b/interface/resources/avatar/network-animation.json @@ -0,0 +1,140 @@ +{ + "version": "1.1", + "root": { + "id": "userAnimStateMachine", + "type": "stateMachine", + "data": { + "currentState": "idleAnim", + "states": [ + { + "id": "idleAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "postTransitAnim", "state": "postTransitAnim" }, + { "var": "preTransitAnim", "state": "preTransitAnim" } + ] + }, + { + "id": "preTransitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "transitAnim", "state": "transitAnim" } + ] + }, + { + "id": "transitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "preTransitAnim", "state": "preTransitAnim" }, + { "var": "postTransitAnim", "state": "postTransitAnim" } + ] + }, + { + "id": "postTransitAnim", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "transitAnim", "state": "transitAnim" }, + { "var": "idleAnim", "state": "idleAnim" } + ] + }, + { + "id": "userAnimA", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "userAnimB", "state": "userAnimB" } + ] + }, + { + "id": "userAnimB", + "interpTarget": 6, + "interpDuration": 6, + "transitions": [ + { "var": "idleAnim", "state": "idleAnim" }, + { "var": "userAnimA", "state": "userAnimA" } + ] + } + ] + }, + "children": [ + { + "id": "idleAnim", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "preTransitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 0.0, + "endFrame": 10.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "transitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 11.0, + "endFrame": 11.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "postTransitAnim", + "type": "clip", + "data": { + "url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx", + "startFrame": 22.0, + "endFrame": 49.0, + "timeScale": 1.0, + "loopFlag": false + }, + "children": [] + }, + { + "id": "userAnimA", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "userAnimB", + "type": "clip", + "data": { + "url": "qrc:///avatar/animations/idle.fbx", + "startFrame": 0.0, + "endFrame": 90.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] + } +} diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c3f6579e90..dd56c03d26 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -83,18 +83,10 @@ AvatarManager::AvatarManager(QObject* parent) : const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f; // Based on testing - const QString START_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; - const QString MIDDLE_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; - const QString END_ANIMATION_URL = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx"; - _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; _transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE; _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; _transitConfig._isDistanceBased = true; - - _transitConfig._startTransitAnimation = AvatarTransit::TransitAnimation(START_ANIMATION_URL, 0, 10); - _transitConfig._middleTransitAnimation = AvatarTransit::TransitAnimation(MIDDLE_ANIMATION_URL, 15, 0); - _transitConfig._endTransitAnimation = AvatarTransit::TransitAnimation(END_ANIMATION_URL, 22, 27); } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { @@ -143,30 +135,22 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) { } void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { - auto startAnimation = _transitConfig._startTransitAnimation; - auto middleAnimation = _transitConfig._middleTransitAnimation; - auto endAnimation = _transitConfig._endTransitAnimation; - - const float REFERENCE_FPS = 30.0f; switch (status) { case AvatarTransit::Status::STARTED: qDebug() << "START_FRAME"; - _myAvatar->overrideNetworkAnimation(startAnimation._animationUrl, REFERENCE_FPS, false, startAnimation._firstFrame, - startAnimation._firstFrame + startAnimation._frameCount); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("preTransitAnim"); break; case AvatarTransit::Status::START_TRANSIT: qDebug() << "START_TRANSIT"; - _myAvatar->overrideNetworkAnimation(middleAnimation._animationUrl, REFERENCE_FPS, false, middleAnimation._firstFrame, - middleAnimation._firstFrame + middleAnimation._frameCount); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("transitAnim"); break; case AvatarTransit::Status::END_TRANSIT: qDebug() << "END_TRANSIT"; - _myAvatar->overrideNetworkAnimation(endAnimation._animationUrl, REFERENCE_FPS, false, endAnimation._firstFrame, - endAnimation._firstFrame + endAnimation._frameCount); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("postTransitAnim"); break; case AvatarTransit::Status::ENDED: qDebug() << "END_FRAME"; - _myAvatar->restoreNetworkAnimation(); + _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("idleAnim"); break; case AvatarTransit::Status::PRE_TRANSIT: break; @@ -188,9 +172,6 @@ void AvatarManager::updateMyAvatar(float deltaTime) { AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); handleTransitAnimations(status); - bool sendFirstTransitPacket = (status == AvatarTransit::Status::STARTED); - bool sendCurrentTransitPacket = (status != AvatarTransit::Status::IDLE && (status != AvatarTransit::Status::POST_TRANSIT || status != AvatarTransit::Status::ENDED)); - _myAvatar->update(deltaTime); render::Transaction transaction; _myAvatar->updateRenderItem(transaction); @@ -199,19 +180,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) { quint64 now = usecTimestampNow(); quint64 dt = now - _lastSendAvatarDataTime; - if (sendFirstTransitPacket || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused)) { + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) { // send head/hand data to the avatar mixer and voxel server - PerformanceTimer perfTimer("send"); - if (sendFirstTransitPacket) { - _myAvatar->overrideNextPacketPositionData(_myAvatar->getTransit()->getEndPosition()); - } else if (sendCurrentTransitPacket) { - _myAvatar->overrideNextPacketPositionData(_myAvatar->getTransit()->getCurrentPosition()); - } + PerformanceTimer perfTimer("send"); _myAvatar->sendAvatarDataPacket(); _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); } - } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 152215e717..b347963cf1 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1124,15 +1124,6 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float _skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame); } -void MyAvatar::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "overrideNetworkAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), - Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame)); - return; - } - _skeletonModel->getRig().overrideNetworkAnimation(url, fps, loop, firstFrame, lastFrame); -} - void MyAvatar::restoreAnimation() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "restoreAnimation"); @@ -1141,14 +1132,6 @@ void MyAvatar::restoreAnimation() { _skeletonModel->getRig().restoreAnimation(); } -void MyAvatar::restoreNetworkAnimation() { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "restoreNetworkAnimation"); - return; - } - _skeletonModel->getRig().restoreNetworkAnimation(); -} - QStringList MyAvatar::getAnimationRoles() { if (QThread::currentThread() != thread()) { QStringList result; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 9770a5bb1a..16b765711a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -376,7 +376,6 @@ public: * }, 3000); */ Q_INVOKABLE void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - Q_INVOKABLE void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); /**jsdoc * The avatar animation system includes a set of default animations along with rules for how those animations are blended together with @@ -393,7 +392,6 @@ public: * }, 3000); */ Q_INVOKABLE void restoreAnimation(); - Q_INVOKABLE void restoreNetworkAnimation(); /**jsdoc * Each avatar has an avatar-animation.json file that defines which animations are used and how they are blended together with procedural data diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index eba361fd4c..92f4c01dcc 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -51,6 +51,8 @@ public: bool getMirrorFlag() const { return _mirrorFlag; } void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; } + float getFrame() const { return _frame; } + void loadURL(const QString& url); protected: diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index b9e654964a..df7e322da7 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -31,6 +31,7 @@ #include "AnimSkeleton.h" #include "AnimUtil.h" #include "IKTarget.h" +#include "PathUtils.h" static int nextRigId = 1; @@ -133,41 +134,28 @@ void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firs _animVars.set("userAnimB", clipNodeEnum == UserAnimState::B); } -void Rig::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) { - UserAnimState::ClipNodeEnum clipNodeEnum; - if (_networkAnimState.clipNodeEnum == UserAnimState::None || _networkAnimState.clipNodeEnum == UserAnimState::B) { - clipNodeEnum = UserAnimState::A; +void Rig::triggerNetworkAnimation(const QString& animName) { + _networkVars.set("idleAnim", false); + _networkVars.set("preTransitAnim", false); + _networkVars.set("transitAnim", false); + _networkVars.set("postTransitAnim", false); + _sendNetworkNode = true; + + if (animName == "idleAnim") { + _networkVars.set("idleAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::Idle; + _sendNetworkNode = false; + } else if (animName == "preTransitAnim") { + _networkVars.set("preTransitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::PreTransit; + } else if (animName == "transitAnim") { + _networkVars.set("transitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::Transit; + } else if (animName == "postTransitAnim") { + _networkVars.set("postTransitAnim", true); + _networkAnimState.clipNodeEnum = NetworkAnimState::PostTransit; } - else { - clipNodeEnum = UserAnimState::B; - } - if (_networkNode) { - // find an unused AnimClip clipNode - _sendNetworkNode = true; - std::shared_ptr clip; - if (clipNodeEnum == UserAnimState::A) { - clip = std::dynamic_pointer_cast(_networkNode->findByName("userAnimA")); - } - else { - clip = std::dynamic_pointer_cast(_networkNode->findByName("userAnimB")); - } - if (clip) { - // set parameters - clip->setLoopFlag(loop); - clip->setStartFrame(firstFrame); - clip->setEndFrame(lastFrame); - const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; - clip->setTimeScale(timeScale); - clip->loadURL(url); - } - } - // store current user anim state. - _networkAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame }; - // notify the userAnimStateMachine the desired state. - _networkVars.set("userAnimNone", false); - _networkVars.set("userAnimA", clipNodeEnum == UserAnimState::A); - _networkVars.set("userAnimB", clipNodeEnum == UserAnimState::B); + } void Rig::restoreAnimation() { @@ -182,13 +170,12 @@ void Rig::restoreAnimation() { } void Rig::restoreNetworkAnimation() { - _sendNetworkNode = false; - if (_networkAnimState.clipNodeEnum != UserAnimState::None) { - _networkAnimState.clipNodeEnum = UserAnimState::None; - // notify the userAnimStateMachine the desired state. - _networkVars.set("userAnimNone", true); - _networkVars.set("userAnimA", false); - _networkVars.set("userAnimB", false); + if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) { + _networkAnimState.clipNodeEnum = NetworkAnimState::Idle; + _networkVars.set("idleAnim", true); + _networkVars.set("preTransitAnim", false); + _networkVars.set("transitAnim", false); + _networkVars.set("postTransitAnim", false); } } @@ -1137,6 +1124,24 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); if (_networkNode) { _networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut); + float alpha = 1.0f; + std::shared_ptr clip; + if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { + clip = std::dynamic_pointer_cast(_networkNode->findByName("preTransitAnim")); + if (clip) { + alpha = (clip->getFrame() - clip->getStartFrame()) / 6.0f; + } + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { + clip = std::dynamic_pointer_cast(_networkNode->findByName("postTransitAnim")); + if (clip) { + alpha = (clip->getEndFrame() - clip->getFrame()) / 6.0f; + } + } + if (_sendNetworkNode) { + for (int i = 0; i < _networkPoseSet._relativePoses.size(); i++) { + _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha)); + } + } } if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) { // animations haven't fully loaded yet. @@ -1798,10 +1803,10 @@ void Rig::initAnimGraph(const QUrl& url) { // load the anim graph _animLoader.reset(new AnimNodeLoader(url)); - _networkLoader.reset(new AnimNodeLoader(url)); - + auto networkUrl = PathUtils::resourcesUrl("avatar/network-animation.json"); + _networkLoader.reset(new AnimNodeLoader(networkUrl)); std::weak_ptr weakSkeletonPtr = _animSkeleton; - connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { + connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, url](AnimNode::Pointer nodeIn) { _animNode = nodeIn; // abort load if the previous skeleton was deleted. @@ -1828,10 +1833,10 @@ void Rig::initAnimGraph(const QUrl& url) { emit onLoadComplete(); }); connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { - qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; + qCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; }); - connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { + connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, networkUrl](AnimNode::Pointer nodeIn) { _networkNode = nodeIn; // abort load if the previous skeleton was deleted. auto sharedSkeletonPtr = weakSkeletonPtr.lock(); @@ -1839,16 +1844,22 @@ void Rig::initAnimGraph(const QUrl& url) { return; } _networkNode->setSkeleton(sharedSkeletonPtr); - if (_networkAnimState.clipNodeEnum != UserAnimState::None) { + if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) { // restore the user animation we had before reset. - UserAnimState origState = _networkAnimState; - _networkAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; - overrideNetworkAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame); + NetworkAnimState origState = _networkAnimState; + _networkAnimState = { NetworkAnimState::Idle, "", 30.0f, false, 0.0f, 0.0f }; + if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { + triggerNetworkAnimation("preTransitAnim"); + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::Transit) { + triggerNetworkAnimation("transitAnim"); + } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { + triggerNetworkAnimation("postTransitAnim"); + } } - // emit onLoadComplete(); + }); - connect(_networkLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) { - qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; + connect(_networkLoader.get(), &AnimNodeLoader::error, [networkUrl](int error, QString str) { + qCritical(animation) << "Error loading" << networkUrl.toDisplayString() << "code = " << error << "str =" << str; }); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 37d1ec1dd3..af786a5e70 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -113,7 +113,7 @@ public: void destroyAnimGraph(); void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); - void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); + void triggerNetworkAnimation(const QString& animName); void restoreAnimation(); void restoreNetworkAnimation(); @@ -323,6 +323,25 @@ protected: RigRole _state { RigRole::Idle }; RigRole _desiredState { RigRole::Idle }; float _desiredStateAge { 0.0f }; + + struct NetworkAnimState { + enum ClipNodeEnum { + Idle = 0, + PreTransit, + Transit, + PostTransit + }; + NetworkAnimState() : clipNodeEnum(NetworkAnimState::Idle) {} + NetworkAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) : + clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {} + + ClipNodeEnum clipNodeEnum; + QString url; + float fps; + bool loop; + float firstFrame; + float lastFrame; + }; struct UserAnimState { enum ClipNodeEnum { @@ -357,7 +376,7 @@ protected: }; UserAnimState _userAnimState; - UserAnimState _networkAnimState; + NetworkAnimState _networkAnimState; std::map _roleAnimStates; float _leftHandOverlayAlpha { 0.0f }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index b43e1c23f6..705ebc0b13 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -114,12 +114,10 @@ void Avatar::setShowNamesAboveHeads(bool show) { } AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { - bool checkDistance = (!_isActive || (_status == Status::POST_TRANSIT || _status == Status::ENDED)); float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); - const float TRANSIT_TRIGGER_MAX_DISTANCE = 30.0f; float scaledMaxTransitDistance = TRANSIT_TRIGGER_MAX_DISTANCE * _scale; - if (oneFrameDistance > config._triggerDistance && checkDistance) { + if (oneFrameDistance > config._triggerDistance) { if (oneFrameDistance < scaledMaxTransitDistance) { start(deltaTime, _lastPosition, avatarPosition, config); } else { @@ -132,12 +130,9 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av const float SETTLE_ABORT_DISTANCE = 0.1f; float scaledSettleAbortDistance = SETTLE_ABORT_DISTANCE * _scale; - bool abortPostTransit = (_status == Status::POST_TRANSIT && _purpose == Purpose::SIT) || - (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT); - if (abortPostTransit) { + if (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT) { reset(); _status = Status::ENDED; - _purpose = Purpose::UNDEFINED; } return _status; } @@ -154,10 +149,13 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const _transitLine = endPosition - startPosition; _totalDistance = glm::length(_transitLine); _easeType = config._easeType; - const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - _preTransitTime = (float)config._startTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; - _postTransitTime = (float)config._endTransitAnimation._frameCount / REFERENCE_FRAMES_PER_SECOND; + const float REFERENCE_FRAMES_PER_SECOND = 30.0f; + const float PRE_TRANSIT_FRAME_COUNT = 10.0f; + const float POST_TRANSIT_FRAME_COUNT = 27.0f; + + _preTransitTime = PRE_TRANSIT_FRAME_COUNT / REFERENCE_FRAMES_PER_SECOND; + _postTransitTime = POST_TRANSIT_FRAME_COUNT / REFERENCE_FRAMES_PER_SECOND; int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; _totalTime = _transitTime + _preTransitTime + _postTransitTime; @@ -206,7 +204,6 @@ AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) { _currentPosition = _endPosition; if (nextTime >= _totalTime) { _isActive = false; - _purpose = Purpose::UNDEFINED; status = Status::ENDED; } else if (_currentTime < _totalTime - _postTransitTime) { status = Status::END_TRANSIT; @@ -2007,11 +2004,6 @@ void Avatar::setTransitScale(float scale) { _transit.setScale(scale); } -void Avatar::setTransitPurpose(int purpose) { - std::lock_guard lock(_transitLock); - _transit.setPurpose(static_cast(purpose)); -} - void Avatar::overrideNextPacketPositionData(const glm::vec3& position) { std::lock_guard lock(_transitLock); _overrideGlobalPosition = true; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index d375909609..2995c0f7b4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -71,21 +71,6 @@ public: EASE_IN_OUT }; - enum Purpose { - UNDEFINED = 0, - TELEPORT, - SIT - }; - - struct TransitAnimation { - TransitAnimation(){}; - TransitAnimation(const QString& animationUrl, int firstFrame, int frameCount) : - _firstFrame(firstFrame), _frameCount(frameCount), _animationUrl(animationUrl){}; - int _firstFrame; - int _frameCount; - QString _animationUrl; - }; - struct TransitConfig { TransitConfig() {}; int _totalFrames { 0 }; @@ -93,9 +78,6 @@ public: bool _isDistanceBased { false }; float _triggerDistance { 0 }; EaseType _easeType { EaseType::EASE_OUT }; - TransitAnimation _startTransitAnimation; - TransitAnimation _middleTransitAnimation; - TransitAnimation _endTransitAnimation; }; AvatarTransit() {}; @@ -105,7 +87,6 @@ public: glm::vec3 getCurrentPosition() { return _currentPosition; } glm::vec3 getEndPosition() { return _endPosition; } void setScale(float scale) { _scale = scale; } - void setPurpose(const Purpose& purpose) { _purpose = purpose; } void reset(); private: @@ -130,7 +111,6 @@ private: EaseType _easeType { EaseType::EASE_OUT }; Status _status { Status::IDLE }; float _scale { 1.0f }; - Purpose _purpose { Purpose::UNDEFINED }; }; class Avatar : public AvatarData, public scriptable::ModelProvider { @@ -457,7 +437,6 @@ public: AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); void setTransitScale(float scale); - Q_INVOKABLE void setTransitPurpose(int purpose); void overrideNextPacketPositionData(const glm::vec3& position); diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index d31f207b19..bf5022cdaf 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -781,13 +781,11 @@ Script.include("/~/system/libraries/controllers.js"); if (target === TARGET.NONE || target === TARGET.INVALID) { // Do nothing } else if (target === TARGET.SEAT) { - MyAvatar.setTransitPurpose(2); Entities.callEntityMethod(result.objectID, 'sit'); } else if (target === TARGET.SURFACE || target === TARGET.DISCREPANCY) { var offset = getAvatarFootOffset(); result.intersection.y += offset; var shouldLandSafe = target === TARGET.DISCREPANCY; - MyAvatar.setTransitPurpose(1); MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false, shouldLandSafe); HMD.centerUI(); MyAvatar.centerBody(); From e499309c8e38265d0d76237984f8c3ad77dd99ba Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Fri, 19 Oct 2018 06:49:48 -0400 Subject: [PATCH 150/276] Upload interstitial page sound and update link --- .../assets/sounds/crystals_and_voices.mp3 | Bin 0 -> 254192 bytes scripts/system/interstitialPage.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 scripts/system/assets/sounds/crystals_and_voices.mp3 diff --git a/scripts/system/assets/sounds/crystals_and_voices.mp3 b/scripts/system/assets/sounds/crystals_and_voices.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ee9073b0156e2a341fe75e22646f1a52b2a2adae GIT binary patch literal 254192 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 z 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 Date: Fri, 19 Oct 2018 08:16:14 -0400 Subject: [PATCH 151/276] Made MP3 constistant 128kbps and fixed path resolving --- .../assets/sounds/crystals_and_voices.mp3 | Bin 254192 -> 334366 bytes scripts/system/interstitialPage.js | 64 +++++++++--------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/scripts/system/assets/sounds/crystals_and_voices.mp3 b/scripts/system/assets/sounds/crystals_and_voices.mp3 index ee9073b0156e2a341fe75e22646f1a52b2a2adae..1dd2037e6bfce5d4db21e733dd8a31b4d5dae84b 100644 GIT binary patch literal 334366 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�iSZj~m>CA?pj#|5FpF1oHQ;W_1Mcxm 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 z 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 Am;e9( literal 254192 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 z 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 milliseconds){ + if ((new Date().getTime() - start) > milliseconds) { break; } } @@ -503,7 +503,7 @@ if (errorConnectingToDomain) { updateOverlays(errorConnectingToDomain); // setting hover id to invisible - Overlays.editOverlay(loadingToTheSpotHoverID, {visible: false}); + Overlays.editOverlay(loadingToTheSpotHoverID, { visible: false }); endAudio(); currentDomain = "no domain"; timer = null; @@ -519,7 +519,7 @@ } else if ((physicsEnabled && (currentProgress >= (TOTAL_LOADING_PROGRESS - EPSILON)))) { updateOverlays((physicsEnabled || connectionToDomainFailed)); // setting hover id to invisible - Overlays.editOverlay(loadingToTheSpotHoverID, {visible: false}); + Overlays.editOverlay(loadingToTheSpotHoverID, { visible: false }); endAudio(); currentDomain = "no domain"; timer = null; @@ -527,23 +527,23 @@ } timer = Script.setTimeout(update, BASIC_TIMER_INTERVAL_MS); } - var whiteColor = {red: 255, green: 255, blue: 255}; - var greyColor = {red: 125, green: 125, blue: 125}; + var whiteColor = { red: 255, green: 255, blue: 255 }; + var greyColor = { red: 125, green: 125, blue: 125 }; Overlays.mouseReleaseOnOverlay.connect(clickedOnOverlay); Overlays.hoverEnterOverlay.connect(onEnterOverlay); Overlays.hoverLeaveOverlay.connect(onLeaveOverlay); location.hostChanged.connect(domainChanged); - location.lookupResultsFinished.connect(function() { - Script.setTimeout(function() { + location.lookupResultsFinished.connect(function () { + Script.setTimeout(function () { connectionToDomainFailed = !location.isConnected; }, 1200); }); Window.redirectErrorStateChanged.connect(toggleInterstitialPage); MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); - MyAvatar.sessionUUIDChanged.connect(function() { + MyAvatar.sessionUUIDChanged.connect(function () { var avatarSessionUUID = MyAvatar.sessionUUID; Overlays.editOverlay(loadingSphereID, { parentID: avatarSessionUUID }); }); @@ -553,7 +553,7 @@ tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton(BUTTON_PROPERTIES); - button.clicked.connect(function() { + button.clicked.connect(function () { toggle = !toggle; updateOverlays(toggle); }); From fcde53fc65091309d14932193d6423b3149dfac9 Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Fri, 19 Oct 2018 08:22:43 -0400 Subject: [PATCH 152/276] Include black-sphere.fbx and update linked url --- scripts/system/assets/models/black-sphere.fbx | Bin 0 -> 56208 bytes scripts/system/interstitialPage.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 scripts/system/assets/models/black-sphere.fbx diff --git a/scripts/system/assets/models/black-sphere.fbx b/scripts/system/assets/models/black-sphere.fbx new file mode 100644 index 0000000000000000000000000000000000000000..2e6dea233f762a19946e328df85727020e49d9ec GIT binary patch literal 56208 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 I4%tHgf0O8X)Bpeg literal 0 HcmV?d00001 diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 6655e1ef9d..d54a8415a0 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -64,7 +64,7 @@ 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", + url: Script.resolvePath("/~/system/assets/models/black-sphere.fbx"), dimensions: DEFAULT_DIMENSIONS, alpha: 1, visible: isVisible, From ba459eda9edc2f653acf95d84f68f57abcd3b6ad Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Fri, 19 Oct 2018 08:41:04 -0400 Subject: [PATCH 153/276] Removed duplicate backgroundAlpha --- scripts/system/interstitialPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index d54a8415a0..7ee4abb7d1 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -112,7 +112,6 @@ backgroundAlpha: 1, lineHeight: 0.13, visible: isVisible, - backgroundAlpha: 0, ignoreRayIntersection: true, drawInFront: true, grabbable: false, From 5db7c7821e81537935a88912858c4b62107d4444 Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Fri, 19 Oct 2018 08:51:13 -0400 Subject: [PATCH 154/276] Proper formatting (aka no spaces after anonymous funcs) --- scripts/system/interstitialPage.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 7ee4abb7d1..a5a952444d 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -12,7 +12,7 @@ /* global Script, Controller, Overlays, Quat, MyAvatar, Entities, print, Vec3, AddressManager, Render, Window, Toolbars, Camera, HMD, location, Account, Xform*/ -(function () { +(function() { Script.include("/~/system/libraries/Xform.js"); var DEBUG = false; var MIN_LOADING_PROGRESS = 3.6; @@ -310,7 +310,7 @@ var url = Account.metaverseServerURL + '/api/v1/places/' + domain; request({ uri: url - }, function (error, data) { + }, function(error, data) { if (data.status === "success") { var domainInfo = data.data; var domainDescriptionText = domainInfo.place.description; @@ -534,15 +534,15 @@ Overlays.hoverLeaveOverlay.connect(onLeaveOverlay); location.hostChanged.connect(domainChanged); - location.lookupResultsFinished.connect(function () { - Script.setTimeout(function () { + location.lookupResultsFinished.connect(function() { + Script.setTimeout(function() { connectionToDomainFailed = !location.isConnected; }, 1200); }); Window.redirectErrorStateChanged.connect(toggleInterstitialPage); MyAvatar.sensorToWorldScaleChanged.connect(scaleInterstitialPage); - MyAvatar.sessionUUIDChanged.connect(function () { + MyAvatar.sessionUUIDChanged.connect(function() { var avatarSessionUUID = MyAvatar.sessionUUID; Overlays.editOverlay(loadingSphereID, { parentID: avatarSessionUUID }); }); @@ -552,7 +552,7 @@ tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton(BUTTON_PROPERTIES); - button.clicked.connect(function () { + button.clicked.connect(function() { toggle = !toggle; updateOverlays(toggle); }); From 1a883b5a32b6f79cd9a08f37afcbf8d96e89ce17 Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Fri, 19 Oct 2018 09:04:22 -0400 Subject: [PATCH 155/276] Added HifiAbout (used in interstitialPage.js) --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 67921be395..9900825b23 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -36,6 +36,7 @@ module.exports = { "GlobalServices": false, "GooglePoly": false, "Graphics": false, + "HifiAbout": false, "HMD": false, "LaserPointers": false, "location": true, From 0837f790ebd748add984b1ac2cd21a34909fa512 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 19 Oct 2018 07:46:16 -0700 Subject: [PATCH 156/276] combo box changes --- .../resources/qml/hifi/avatarapp/Settings.qml | 23 +++++++++++++++++-- interface/src/avatar/MyAvatar.cpp | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index fd72d70106..64ae03237c 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -366,7 +366,7 @@ Rectangle { // sit stand combo box HifiControlsUit.ComboBox { id: boxy - //textRole: "text" + comboBox.textRole: "text" currentIndex: 2 model: ListModel { id: cbItems @@ -378,7 +378,26 @@ Rectangle { //displayText: "fred" //label: cbItems.get(currentIndex).text width: 200 - onCurrentIndexChanged: { + onCurrentIndexChanged: { + if (cbItems.get(currentIndex).text === "Force Sitting") { + sitRadiobutton.checked = true + lockSitStandStateCheckbox.checked = true + settings.lockStateEnabled = true + settings.sittingEnabled = true + } else if (cbItems.get(currentIndex).text === "Force Standing") { + sitRadiobutton.checked = false + lockSitStandStateCheckbox.checked = true + settings.lockStateEnabled = true + settings.sittingEnabled = false + } else if (cbItems.get(currentIndex).text === "auto") { + sitRadiobutton.checked = false + lockSitStandStateCheckbox.checked = false + settings.lockStateEnabled = false + settings.sittingEnabled = false + } else if (cbItems.get(currentIndex).text === "Disable Recentering") { + settings.lockStateEnabled = false + settings.sittingEnabled = false + } console.debug(cbItems.get(currentIndex).text + ", " + cbItems.get(currentIndex).color) console.debug("line 2") } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2c9b83b636..95141d614f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -533,6 +533,7 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); + qCDebug(interfaceapp) << " the sit state is " << _isInSittingState.get() << " the lock is " << _lockSitStandState.get(); // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter From ef740140750e2ef1a143f86f2d2672e883ccfd91 Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Fri, 19 Oct 2018 13:55:56 -0700 Subject: [PATCH 157/276] Small avatars now have a minimum jump height of 0.25 meters This should improve the quality of the jump animation and improve the mobility of small avatars. --- interface/src/avatar/MySkeletonModel.cpp | 2 +- libraries/animation/src/Rig.cpp | 16 ++++++++++++---- libraries/animation/src/Rig.h | 3 ++- libraries/physics/src/CharacterController.cpp | 8 +++++--- libraries/shared/src/AvatarConstants.h | 7 ++++--- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index c1a49d7a10..08a1e190f1 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -298,7 +298,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { auto velocity = myAvatar->getLocalVelocity() / myAvatar->getSensorToWorldScale(); auto position = myAvatar->getLocalPosition(); auto orientation = myAvatar->getLocalOrientation(); - _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState); + _rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState, myAvatar->getSensorToWorldScale()); // evaluate AnimGraph animation and update jointStates. Model::updateRig(deltaTime, parentTransform); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 341b554949..335cddf218 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -30,6 +30,7 @@ #include "AnimOverlay.h" #include "AnimSkeleton.h" #include "AnimUtil.h" +#include "AvatarConstants.h" #include "IKTarget.h" @@ -629,7 +630,8 @@ bool Rig::getRelativeDefaultJointTranslation(int index, glm::vec3& translationOu } } -void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState) { +void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, + const glm::quat& worldRotation, CharacterControllerState ccState, float sensorToWorldScale) { glm::vec3 forward = worldRotation * IDENTITY_FORWARD; glm::vec3 workingVelocity = worldVelocity; @@ -924,9 +926,15 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } _animVars.set("isNotInAir", false); - // compute blend based on velocity - const float JUMP_SPEED = 3.5f; - float alpha = glm::clamp(-workingVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f; + // We want to preserve the apparent jump height in sensor space. + const float jumpHeight = std::max(sensorToWorldScale * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + + // convert jump height to a initial jump speed with the given gravity. + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + + // compute inAirAlpha blend based on velocity + float alpha = glm::clamp((-workingVelocity.y * sensorToWorldScale) / jumpSpeed, -1.0f, 1.0f) + 1.0f; + _animVars.set("inAirAlpha", alpha); } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index ed0b70d4b6..5330a06a01 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -172,7 +172,8 @@ public: AnimPose getJointPose(int jointIndex) const; // Start or stop animations as needed. - void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState); + void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, + const glm::quat& worldRotation, CharacterControllerState ccState, float sensorToWorldScale); // Regardless of who started the animations or how many, update the joints. void updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform); diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 626184d1dc..8fd6d4eada 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -766,14 +766,16 @@ void CharacterController::updateState() { SET_STATE(State::InAir, "takeoff done"); // compute jumpSpeed based on the scaled jump height for the default avatar in default gravity. - float jumpSpeed = sqrtf(2.0f * DEFAULT_AVATAR_GRAVITY * _scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT); + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); velocity += jumpSpeed * _currentUp; _rigidBody->setLinearVelocity(velocity); } break; case State::InAir: { - const float JUMP_SPEED = _scaleFactor * DEFAULT_AVATAR_JUMP_SPEED; - if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { SET_STATE(State::Ground, "hit ground"); } else if (_flyingAllowed) { btVector3 desiredVelocity = _targetVelocity; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index 6c38d08c96..87da47a27a 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -70,9 +70,10 @@ 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 -const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AVATAR_JUMP_SPEED) / (2.0f * DEFAULT_AVATAR_GRAVITY); // meters +const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 (world) +const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second (sensor) +const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AVATAR_JUMP_SPEED) / (2.0f * -DEFAULT_AVATAR_GRAVITY); // meters (sensor) +const float DEFAULT_AVATAR_MIN_JUMP_HEIGHT = 0.25f; // meters (world) // hack const float DEFAULT_AVATAR_FALL_HEIGHT = 20.0f; // meters const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // meters From 31d099907aea02a6a11ca357b86ef80d3896413a Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 19 Oct 2018 14:00:52 -0700 Subject: [PATCH 158/276] Fix warnings --- 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 df7e322da7..35f076c78d 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1138,7 +1138,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons } } if (_sendNetworkNode) { - for (int i = 0; i < _networkPoseSet._relativePoses.size(); i++) { + for (auto i = 0; i < _networkPoseSet._relativePoses.size(); i++) { _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha)); } } From 71ec5f3612713cfae43c20dabc3c9986f60a4309 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 20 Oct 2018 11:57:46 +1300 Subject: [PATCH 159/276] Fix tablet highlighting when using the Create and Shapes apps --- .../controllers/controllerModules/inEditMode.js | 11 ++++++----- .../controllers/controllerModules/inVREditMode.js | 13 ++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 1917505bd8..2b17f447a0 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -158,11 +158,12 @@ Script.include("/~/system/libraries/utils.js"); } } - var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND - ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); - if (nearOverlay) { - var nearOverlayReady = nearOverlay.isReady(controllerData); - if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) { + // Tablet highlight and grabbing. + var tabletHighlight = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"); + if (tabletHighlight) { + var tabletHighlightReady = tabletHighlight.isReady(controllerData); + if (tabletHighlightReady.active) { return this.exitModule(); } } diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index 65b6744646..0c04918ab1 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -101,13 +101,12 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } } - // Tablet grabbing. - var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND ? - "RightNearParentingGrabOverlay" : - "LeftNearParentingGrabOverlay"); - if (nearOverlay) { - var nearOverlayReady = nearOverlay.isReady(controllerData); - if (nearOverlayReady.active && HMD.tabletID && nearOverlay.grabbedThingID === HMD.tabletID) { + // Tablet highlight and grabbing. + var tabletHighlight = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"); + if (tabletHighlight) { + var tabletHighlightReady = tabletHighlight.isReady(controllerData); + if (tabletHighlightReady.active) { return this.exitModule(); } } From e59f1516efc6d2f9ea5ab8ba7cb2848696101485 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 19 Oct 2018 16:01:53 -0700 Subject: [PATCH 160/276] CR changes --- scripts/system/html/css/edit-style.css | 14 +- scripts/system/html/js/entityProperties.js | 190 ++++++++++++++------- 2 files changed, 133 insertions(+), 71 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8205fcd340..c4ab00e689 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -614,8 +614,8 @@ hr { margin-top: 0; } -.checkbox-sub-props { - margin-top: 18px; +.checkbox-sub-props { + margin-top: 18px; } .property .number { @@ -969,12 +969,12 @@ div.refresh input[type="button"] { margin-top: 6px; } -fieldset .checkbox-sub-props { - margin-top: 0; -} +fieldset .checkbox-sub-props { + margin-top: 0; +} -fieldset .checkbox-sub-props .property:first-child { - margin-top: 0; +fieldset .checkbox-sub-props .property:first-child { + margin-top: 0; } .column { diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 1a52cf939d..d3e31e4b9b 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1,6 +1,7 @@ // entityProperties.js // // Created by Ryan Huffman on 13 Nov 2014 +// Modified by David Back on 19 Oct 2018 // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -26,9 +27,7 @@ const ICON_FOR_TYPE = { Material: "" }; -const PI = 3.14159265358979; -const DEGREES_TO_RADIANS = PI / 180.0; -const RADIANS_TO_DEGREES = 180.0 / PI; +const DEGREES_TO_RADIANS = Math.PI / 180.0; const NO_SELECTION = "No selection"; @@ -564,7 +563,7 @@ const GROUPS = [ type: "vec2", vec2Type: "xy", min: 0, - min: 1, + max: 1, step: 0.1, decimals: 4, subLabels: [ "x", "y" ], @@ -883,7 +882,7 @@ const GROUPS = [ propertyID: "azimuthFinish", }, { - label: "Verical Angle Start", + label: "Vertical Angle Start", type: "slider", min: 0, max: 180, @@ -893,7 +892,7 @@ const GROUPS = [ propertyID: "polarStart", }, { - label: "Verical Angle Finish", + label: "Vertical Angle Finish", type: "slider", min: 0, max: 180, @@ -1233,12 +1232,53 @@ const GROUPS_PER_TYPE = { const EDITOR_TIMEOUT_DURATION = 1500; const DEBOUNCE_TIMEOUT = 125; + +const COLOR_MIN = 0; +const COLOR_MAX = 255; +const COLOR_STEP = 1; + const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MATERIAL_PREFIX_STRING = "mat::"; const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; +const PROPERTY_NAME_DIVISION = { + GROUP: 0, + PROPERTY: 1, + SUBPROPERTY: 2, +}; + +const VECTOR_ELEMENTS = { + X_INPUT: 0, + Y_INPUT: 1, + Z_INPUT: 2, +}; + +const COLOR_ELEMENTS = { + COLOR_PICKER: 0, + RED_INPUT: 1, + GREEN_INPUT: 2, + BLUE_INPUT: 3, +}; + +const SLIDER_ELEMENTS = { + SLIDER: 0, + NUMBER_INPUT: 1, +}; + +const ICON_ELEMENTS = { + ICON: 0, + LABEL: 1, +}; + +const TEXTURE_ELEMENTS = { + IMAGE: 0, + TEXT_INPUT: 1, +}; + +const JSON_EDITOR_ROW_DIV_INDEX = 2; + var elGroups = {}; var properties = {}; var colorPickers = {}; @@ -1420,26 +1460,26 @@ function showGroupsForType(type) { } } -function getPropertyValue(propertyName) { +function getPropertyValue(originalPropertyName) { // if this is a compound property name (i.e. animation.running) // then split it by . up to 3 times to find property value let propertyValue; - let splitPropertyName = propertyName.split('.'); + let splitPropertyName = originalPropertyName.split('.'); if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; let groupProperties = selectedEntityProperties[propertyGroupName]; - if (groupProperties === undefined || groupProperties[subPropertyName] === undefined) { + if (groupProperties === undefined || groupProperties[propertyName] === undefined) { return undefined; } - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - propertyValue = groupProperties[subPropertyName][subSubPropertyName]; + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUBPROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUBPROPERTY]; + propertyValue = groupProperties[propertyName][subPropertyName]; } else { - propertyValue = groupProperties[subPropertyName]; + propertyValue = groupProperties[propertyName]; } } else { - propertyValue = selectedEntityProperties[propertyName]; + propertyValue = selectedEntityProperties[originalPropertyName]; } return propertyValue; } @@ -1449,23 +1489,23 @@ function getPropertyValue(propertyName) { * PROPERTY UPDATE FUNCTIONS */ -function updateProperty(propertyName, propertyValue) { +function updateProperty(originalPropertyName, propertyValue) { let propertyUpdate = {}; // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times - let splitPropertyName = propertyName.split('.'); + let splitPropertyName = originalPropertyName.split('.'); if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[0]; - let subPropertyName = splitPropertyName[1]; + let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; + let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; propertyUpdate[propertyGroupName] = {}; - if (splitPropertyName.length === 3) { - let subSubPropertyName = splitPropertyName[2]; - propertyUpdate[propertyGroupName][subPropertyName] = {}; - propertyUpdate[propertyGroupName][subPropertyName][subSubPropertyName] = propertyValue; + if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUBPROPERTY + 1) { + let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUBPROPERTY]; + propertyUpdate[propertyGroupName][propertyName] = {}; + propertyUpdate[propertyGroupName][propertyName][subPropertyName] = propertyValue; } else { - propertyUpdate[propertyGroupName][subPropertyName] = propertyValue; + propertyUpdate[propertyGroupName][propertyName] = propertyValue; } } else { - propertyUpdate[propertyName] = propertyValue; + propertyUpdate[originalPropertyName] = propertyValue; } updateProperties(propertyUpdate); } @@ -1719,7 +1759,10 @@ function createSliderProperty(property, elProperty, elLabel) { elDiv.appendChild(elInput); elProperty.appendChild(elDiv); - return [ elSlider, elInput ]; + let elResult = []; + elResult[SLIDER_ELEMENTS.SLIDER] = elSlider; + elResult[SLIDER_ELEMENTS.NUMBER_INPUT] = elInput; + return elResult; } function createVec3Property(property, elProperty, elLabel) { @@ -1737,11 +1780,11 @@ function createVec3Property(property, elProperty, elLabel) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[0], + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_INPUT], propertyData.min, propertyData.max, propertyData.step); - let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT], propertyData.min, propertyData.max, propertyData.step); - let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[2], + let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_INPUT], propertyData.min, propertyData.max, propertyData.step); let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, @@ -1750,7 +1793,11 @@ function createVec3Property(property, elProperty, elLabel) { elInputY.addEventListener('change', inputChangeFunction); elInputZ.addEventListener('change', inputChangeFunction); - return [ elInputX, elInputY, elInputZ ]; + let elResult = []; + elResult[VECTOR_ELEMENTS.X_INPUT] = elInputX; + elResult[VECTOR_ELEMENTS.Y_INPUT] = elInputY; + elResult[VECTOR_ELEMENTS.Z_INPUT] = elInputZ; + return elResult; } function createVec2Property(property, elProperty, elLabel) { @@ -1768,9 +1815,9 @@ function createVec2Property(property, elProperty, elLabel) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[0], + let elInputX = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.X_INPUT], propertyData.min, propertyData.max, propertyData.step); - let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[1], + let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT], propertyData.min, propertyData.max, propertyData.step); let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, @@ -1778,7 +1825,10 @@ function createVec2Property(property, elProperty, elLabel) { elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); - return [elInputX, elInputY]; + let elResult = []; + elResult[VECTOR_ELEMENTS.X_INPUT] = elInputX; + elResult[VECTOR_ELEMENTS.Y_INPUT] = elInputY; + return elResult; } function createColorProperty(property, elProperty, elLabel) { @@ -1798,9 +1848,9 @@ function createColorProperty(property, elProperty, elLabel) { elProperty.appendChild(elLabel); elProperty.appendChild(elTuple); - let elInputR = createTupleNumberInput(elTuple, elementID, "red", 0, 255, 1); - let elInputG = createTupleNumberInput(elTuple, elementID, "green", 0, 255, 1); - let elInputB = createTupleNumberInput(elTuple, elementID, "blue", 0, 255, 1); + let elInputR = createTupleNumberInput(elTuple, elementID, "red", COLOR_MIN, COLOR_MAX, COLOR_STEP); + let elInputG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP); + let elInputB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP); let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); elInputR.addEventListener('change', inputChangeFunction); @@ -1832,7 +1882,12 @@ function createColorProperty(property, elProperty, elLabel) { } }); - return [elColorPicker, elInputR, elInputG, elInputB]; + let elResult = []; + elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker; + elResult[COLOR_ELEMENTS.RED_INPUT] = elInputR; + elResult[COLOR_ELEMENTS.GREEN_INPUT] = elInputG; + elResult[COLOR_ELEMENTS.BLUE_INPUT] = elInputB; + return elResult; } function createDropdownProperty(property, propertyID, elProperty, elLabel) { @@ -1902,7 +1957,10 @@ function createIconProperty(property, elProperty, elLabel) { elProperty.appendChild(elSpan); elProperty.appendChild(elLabel); - return [ elSpan, elLabel ]; + let elResult = []; + elResult[ICON_ELEMENTS.ICON] = elSpan; + elResult[ICON_ELEMENTS.LABEL] = elLabel; + return elResult; } function createTextureProperty(property, elProperty, elLabel) { @@ -1952,8 +2010,11 @@ function createTextureProperty(property, elProperty, elLabel) { elProperty.appendChild(elLabel); elProperty.appendChild(elDiv); elProperty.appendChild(elInput); - - return [ elImage, elInput ]; + + let elResult = []; + elResult[TEXTURE_ELEMENTS.IMAGE] = elImage; + elResult[TEXTURE_ELEMENTS.TEXT_INPUT] = elInput; + return elResult; } function createButtonsProperty(property, elProperty, elLabel) { @@ -2468,7 +2529,7 @@ function saveJSONMaterialData(noUpdate) { function bindAllNonJSONEditorElements() { var inputs = $('input'); var i; - for (i = 0; i < inputs.length; i++) { + for (i = 0; i < inputs.length; ++i) { var input = inputs[i]; var field = $(input); // TODO FIXME: (JSHint) Functions declared within loops referencing @@ -2497,7 +2558,7 @@ function bindAllNonJSONEditorElements() { function setDropdownText(dropdown) { let lis = dropdown.parentNode.getElementsByTagName("li"); let text = ""; - for (let i = 0; i < lis.length; i++) { + for (let i = 0; i < lis.length; ++i) { if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { text = lis[i].textContent; } @@ -2584,6 +2645,7 @@ function loaded() { let propertyID = propertyData.propertyID; let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID; let propertyElementID = "property-" + propertyID; + propertyElementID = propertyElementID.replace('.', '-'); let elProperty; if (propertyType === "sub-header") { @@ -2644,29 +2706,29 @@ function loaded() { } case 'slider': { let elSlider = createSliderProperty(property, elProperty, elLabel); - properties[propertyID].elSlider = elSlider[0]; - properties[propertyID].elInput = elSlider[1]; + properties[propertyID].elSlider = elSlider[SLIDER_ELEMENTS.SLIDER]; + properties[propertyID].elInput = elSlider[SLIDER_ELEMENTS.NUMBER_INPUT]; break; } case 'vec3': { let elVec3 = createVec3Property(property, elProperty, elLabel); - properties[propertyID].elInputX = elVec3[0]; - properties[propertyID].elInputY = elVec3[1]; - properties[propertyID].elInputZ = elVec3[2]; + properties[propertyID].elInputX = elVec3[VECTOR_ELEMENTS.X_INPUT]; + properties[propertyID].elInputY = elVec3[VECTOR_ELEMENTS.Y_INPUT]; + properties[propertyID].elInputZ = elVec3[VECTOR_ELEMENTS.Z_INPUT]; break; } case 'vec2': { let elVec2 = createVec2Property(property, elProperty, elLabel); - properties[propertyID].elInputX = elVec2[0]; - properties[propertyID].elInputY = elVec2[1]; + properties[propertyID].elInputX = elVec2[VECTOR_ELEMENTS.X_INPUT]; + properties[propertyID].elInputY = elVec2[VECTOR_ELEMENTS.Y_INPUT]; break; } case 'color': { let elColor = createColorProperty(property, elProperty, elLabel); - properties[propertyID].elColorPicker = elColor[0]; - properties[propertyID].elInputR = elColor[1]; - properties[propertyID].elInputG = elColor[2]; - properties[propertyID].elInputB = elColor[3]; + properties[propertyID].elColorPicker = elColor[COLOR_ELEMENTS.COLOR_PICKER]; + properties[propertyID].elInputR = elColor[COLOR_ELEMENTS.RED_INPUT]; + properties[propertyID].elInputG = elColor[COLOR_ELEMENTS.GREEN_INPUT]; + properties[propertyID].elInputB = elColor[COLOR_ELEMENTS.BLUE_INPUT]; break; } case 'dropdown': { @@ -2679,14 +2741,14 @@ function loaded() { } case 'icon': { let elIcon = createIconProperty(property, elProperty, elLabel); - properties[propertyID].elSpan = elIcon[0]; - properties[propertyID].elLabel = elIcon[1]; + properties[propertyID].elSpan = elIcon[ICON_ELEMENTS.ICON]; + properties[propertyID].elLabel = elIcon[ICON_ELEMENTS.LABEL]; break; } case 'texture': { let elTexture = createTextureProperty(property, elProperty, elLabel); - properties[propertyID].elImage = elTexture[0]; - properties[propertyID].elInput = elTexture[1]; + properties[propertyID].elImage = elTexture[TEXTURE_ELEMENTS.IMAGE]; + properties[propertyID].elInput = elTexture[TEXTURE_ELEMENTS.TEXT_INPUT]; break; } case 'buttons': { @@ -2785,7 +2847,7 @@ function loaded() { let types = {}; let numTypes = 0; - for (let i = 0; i < selections.length; i++) { + for (let i = 0; i < selections.length; ++i) { ids.push(selections[i].id); let currentSelectedType = selections[i].properties.type; if (types[currentSelectedType] === undefined) { @@ -2810,7 +2872,7 @@ function loaded() { disableProperties(); } else { - selectedEntityProperties = data.selections[0].properties; + selectedEntityProperties = data.selections[0].properties; if (lastEntityID !== '"' + selectedEntityProperties.id + '"' && lastEntityID !== null) { if (editor !== null) { @@ -3061,7 +3123,7 @@ function loaded() { let elUserDataSaved = document.createElement('span'); elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); elUserDataSaved.innerText = "Saved!"; - elDiv.childNodes[2].appendChild(elUserDataSaved); + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved); elDiv.insertBefore(elStaticUserData, elUserData); elDiv.insertBefore(elUserDataEditor, elUserData); @@ -3077,7 +3139,7 @@ function loaded() { let elMaterialDataSaved = document.createElement('span'); elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); elMaterialDataSaved.innerText = "Saved!"; - elDiv.childNodes[2].appendChild(elMaterialDataSaved); + elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved); elDiv.insertBefore(elStaticMaterialData, elMaterialData); elDiv.insertBefore(elMaterialDataEditor, elMaterialData); @@ -3156,7 +3218,7 @@ function loaded() { for (let optionIndex = 0; optionIndex < options.length; ++optionIndex) { if (options[optionIndex].getAttribute("selected") === "selected") { selectedOption = optionIndex; - // TODO: Shouldn't there be a break here? + break; } } let div = elDropdown.parentNode; @@ -3225,7 +3287,7 @@ function loaded() { // For input and textarea elements, select all of the text on focus let els = document.querySelectorAll("input, textarea"); - for (let i = 0; i < els.length; i++) { + for (let i = 0; i < els.length; ++i) { els[i].onfocus = function (e) { e.target.select(); }; From a43985ef64088a88b02ab2bf11a1788fe611a6cd Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 19 Oct 2018 16:03:53 -0700 Subject: [PATCH 161/276] combo box for recenter model is added --- interface/resources/qml/hifi/AvatarApp.qml | 3 +- .../resources/qml/hifi/avatarapp/Settings.qml | 85 +------------------ interface/src/avatar/MyAvatar.cpp | 35 +++++++- interface/src/avatar/MyAvatar.h | 24 +++++- scripts/system/avatarapp.js | 32 ++----- 5 files changed, 71 insertions(+), 108 deletions(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index bf647b65bb..2d714c0d33 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -252,8 +252,7 @@ Rectangle { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', collisionsEnabled : settings.avatarCollisionsOn, - sittingEnabled : settings.avatarSittingOn, - lockStateEnabled : settings.avatarLockSitStandStateOn, + recenterModel : settings.avatarRecenterModelOn, animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, collisionSoundUrl : settings.avatarCollisionSoundUrl }; diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index 64ae03237c..cd71442bca 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -20,8 +20,7 @@ Rectangle { property real scaleValue: scaleSlider.value / 10 property alias dominantHandIsLeft: leftHandRadioButton.checked property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked - property alias avatarSittingOn: sitRadiobutton.checked - property alias avatarLockSitStandStateOn: lockSitStandStateCheckbox.checked + property alias avatarRecenterModelOn: boxy.currentIndex property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text @@ -47,21 +46,11 @@ Rectangle { collisionsDisabledRadioButton.checked = true; } - if (settings.sittingEnabled) { - sitRadiobutton.checked = true; - } else { - standRadioButton.checked = true; - } - - if (settings.lockStateEnabled) { - lockSitStandStateCheckbox.checked = true; - } else { - lockSitStandStateCheckbox.checked = false; - } - avatarAnimationJSON = settings.animGraphUrl; avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; avatarCollisionSoundUrl = settings.collisionSoundUrl; + print("values " + avatarRecenterModelOn + " " + settings.recenterModel); + avatarRecenterModelOn = settings.recenterModel; visible = true; } @@ -305,7 +294,7 @@ Rectangle { } // TextStyle9 - + RalewaySemiBold { size: 17; Layout.row: 2 @@ -318,51 +307,6 @@ Rectangle { id: sitStand } - HifiControlsUit.RadioButton { - id: sitRadiobutton - - Layout.row: 2 - Layout.column: 1 - Layout.leftMargin: -40 - - ButtonGroup.group: sitStand - checked: true - - colorScheme: hifi.colorSchemes.light - fontSize: 17 - letterSpacing: 1.4 - text: "Sit" - boxSize: 20 - } - - HifiControlsUit.RadioButton { - id: standRadioButton - - Layout.row: 2 - Layout.column: 2 - Layout.rightMargin: 20 - - ButtonGroup.group: sitStand - - colorScheme: hifi.colorSchemes.light - fontSize: 17 - letterSpacing: 1.4 - text: "Stand" - boxSize: 20 - } - - // "Lock State" Checkbox - - HifiControlsUit.CheckBox { - id: lockSitStandStateCheckbox - visible: activeTab == "nearbyTab" - anchors.right: reloadNearbyContainer.left - anchors.rightMargin: 20 - checked: settings.lockStateEnabled - text: "lock" - boxSize: 24 - } - // sit stand combo box HifiControlsUit.ComboBox { id: boxy @@ -375,29 +319,8 @@ Rectangle { ListElement { text: "Auto Mode"; color: "Brown" } ListElement { text: "Disable Recentering"; color: "Red" } } - //displayText: "fred" - //label: cbItems.get(currentIndex).text width: 200 onCurrentIndexChanged: { - if (cbItems.get(currentIndex).text === "Force Sitting") { - sitRadiobutton.checked = true - lockSitStandStateCheckbox.checked = true - settings.lockStateEnabled = true - settings.sittingEnabled = true - } else if (cbItems.get(currentIndex).text === "Force Standing") { - sitRadiobutton.checked = false - lockSitStandStateCheckbox.checked = true - settings.lockStateEnabled = true - settings.sittingEnabled = false - } else if (cbItems.get(currentIndex).text === "auto") { - sitRadiobutton.checked = false - lockSitStandStateCheckbox.checked = false - settings.lockStateEnabled = false - settings.sittingEnabled = false - } else if (cbItems.get(currentIndex).text === "Disable Recentering") { - settings.lockStateEnabled = false - settings.sittingEnabled = false - } console.debug(cbItems.get(currentIndex).text + ", " + cbItems.get(currentIndex).color) console.debug("line 2") } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 95141d614f..14829a7b2e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -533,7 +533,7 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); - qCDebug(interfaceapp) << " the sit state is " << _isInSittingState.get() << " the lock is " << _lockSitStandState.get(); + //qCDebug(interfaceapp) << " the sit state is " << _isInSittingState.get() << " the lock is " << _lockSitStandState.get(); // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter @@ -3867,6 +3867,10 @@ bool MyAvatar::getIsInSittingState() const { return _isInSittingState.get(); } +MyAvatar::SitStandModelType MyAvatar::getRecenterModel() const { + return _recenterModel.get(); +} + bool MyAvatar::getIsSitStandStateLocked() const { return _lockSitStandState.get(); } @@ -3903,9 +3907,38 @@ void MyAvatar::setIsInSittingState(bool isSitting) { setCenterOfGravityModelEnabled(true); } setSitStandStateChange(true); + emit sittingEnabledChanged(isSitting); } +void MyAvatar::setRecenterModel(MyAvatar::SitStandModelType modelName) { + + _recenterModel.set(modelName); + //int temp = 0; + qCDebug(interfaceapp) << "recenter property changed " << modelName; + switch (modelName) { + case SitStandModelType::ForceSit: + setIsInSittingState(true); + setIsSitStandStateLocked(true); + break; + case SitStandModelType::ForceStand: + setIsInSittingState(false); + setIsSitStandStateLocked(true); + break; + case SitStandModelType::Auto: + setIsInSittingState(false); + setIsSitStandStateLocked(false); + break; + case SitStandModelType::DisableHMDLean: + setHMDLeanRecenterEnabled(false); + setIsInSittingState(false); + setIsSitStandStateLocked(false); + break; + } + qCDebug(interfaceapp) << "recenter property changed " << modelName << " sit " << _isInSittingState.get() << " lock " << _lockSitStandState.get(); + emit recenterModelChanged((int)modelName); +} + void MyAvatar::setIsSitStandStateLocked(bool isLocked) { _lockSitStandState.set(isLocked); _sitStandStateTimer = 0.0f; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index be8b5fa1b2..c59bdcd66d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -143,6 +143,7 @@ class MyAvatar : public Avatar { * @property {number} walkBackwardSpeed * @property {number} sprintSpeed * @property {number} isInSittingState + * @property {number} recenterModel * * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the * registration point of the 3D model. @@ -244,6 +245,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed); Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); + Q_PROPERTY(MyAvatar::SitStandModelType recenterModel READ getRecenterModel WRITE setRecenterModel); Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); const QString DOMINANT_LEFT_HAND = "left"; @@ -265,6 +267,15 @@ public: }; Q_ENUM(DriveKeys) + enum SitStandModelType { + ForceSit = 0, + ForceStand, + Auto, + DisableHMDLean, + NumSitStandTypes + }; + Q_ENUM(SitStandModelType) + explicit MyAvatar(QThread* thread); virtual ~MyAvatar(); @@ -1106,6 +1117,8 @@ public: bool getIsInWalkingState() const; void setIsInSittingState(bool isSitting); bool getIsInSittingState() const; + void setRecenterModel(MyAvatar::SitStandModelType modelName); + MyAvatar::SitStandModelType getRecenterModel() const; void setIsSitStandStateLocked(bool isLocked); bool getIsSitStandStateLocked() const; void setWalkSpeed(float value); @@ -1530,6 +1543,14 @@ signals: */ void sittingEnabledChanged(bool enabled); + /**jsdoc + * Triggered when the recenter model is changed + * @function MyAvatar.recenterModelChanged + * @param {int} modeltype + * @ + */ + void recenterModelChanged(int modelName); + /**jsdoc * Triggered when the sit state is enabled or disabled * @function MyAvatar.sitStandStateLockEnabledChanged @@ -1829,7 +1850,7 @@ private: void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); - const float DEFAULT_FLOOR_HEIGHT = 0.0f; + // height of user in sensor space, when standing erect. ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; @@ -1844,6 +1865,7 @@ private: float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; ThreadSafeValueCache _isInSittingState { false }; + ThreadSafeValueCache _recenterModel { MyAvatar::SitStandModelType::Auto }; float _sitStandStateTimer { 0.0f }; float _squatTimer { 0.0f }; float _tippingPoint { _userHeight.get() }; diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 4a25ab9551..d64454c534 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -64,8 +64,7 @@ function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), collisionsEnabled : MyAvatar.getCollisionsEnabled(), - sittingEnabled: MyAvatar.isInSittingState, - lockStateEnabled: MyAvatar.isSitStandStateLocked, + recenterModel: MyAvatar.recenterModel, collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), @@ -138,19 +137,11 @@ function onCollisionsEnabledChanged(enabled) { } } -function onSittingEnabledChanged(isSitting) { - if (currentAvatarSettings.sittingEnabled !== isSitting) { - currentAvatarSettings.sittingEnabled = isSitting; - print("emit sitting changed"); - sendToQml({ 'method': 'settingChanged', 'name': 'sittingEnabled', 'value': isSitting }) - } -} - -function onSitStandStateLockedEnabledChanged(isLocked) { - if (currentAvatarSettings.lockStateEnabled !== isLocked) { - currentAvatarSettings.lockStateEnabled = isLocked; - print("emit lock sit stand state changed"); - sendToQml({ 'method': 'settingChanged', 'name': 'lockStateEnabled', 'value': isLocked }) +function onRecenterModelChanged(modelName) { + if (currentAvatarSettings.recenterModel !== modelName) { + currentAvatarSettings.recenterModel = modelName; + print("emit recenter model changed"); + sendToQml({ 'method': 'settingChanged', 'name': 'recenterModel', 'value': modelName }) } } @@ -329,14 +320,11 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See case 'saveSettings': MyAvatar.setAvatarScale(message.avatarScale); currentAvatar.avatarScale = message.avatarScale; - MyAvatar.setDominantHand(message.settings.dominantHand); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); - MyAvatar.isInSittingState = message.settings.sittingEnabled; - MyAvatar.isSitStandStateLocked = message.settings.lockStateEnabled; + MyAvatar.recenterModel = message.settings.recenterModel; MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); - settings = getMyAvatarSettings(); break; default: @@ -527,8 +515,7 @@ function off() { MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); - MyAvatar.sittingEnabledChanged.disconnect(onSittingEnabledChanged); - MyAvatar.sitStandStateLockEnabledChanged.disconnect(onSitStandStateLockedEnabledChanged); + MyAvatar.recenterModelChanged.disconnect(onRecenterModelChanged); MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); @@ -543,8 +530,7 @@ function on() { MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); - MyAvatar.sittingEnabledChanged.connect(onSittingEnabledChanged); - MyAvatar.sitStandStateLockEnabledChanged.connect(onSitStandStateLockedEnabledChanged); + MyAvatar.recenterModelChanged.connect(onRecenterModelChanged); MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); From a93aa689019cc784bbbfebe635fd7445b66d1e43 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 9 Oct 2018 16:42:56 -0700 Subject: [PATCH 162/276] Add additional safety checks to gl::Uniform::load --- libraries/gl/src/gl/GLShaders.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libraries/gl/src/gl/GLShaders.cpp b/libraries/gl/src/gl/GLShaders.cpp index a0d976d727..b7e80fbeb4 100644 --- a/libraries/gl/src/gl/GLShaders.cpp +++ b/libraries/gl/src/gl/GLShaders.cpp @@ -14,14 +14,17 @@ using namespace gl; void Uniform::load(GLuint glprogram, int index) { this->index = index; - const GLint NAME_LENGTH = 256; - GLchar glname[NAME_LENGTH]; - GLint length = 0; - glGetActiveUniform(glprogram, index, NAME_LENGTH, &length, &size, &type, glname); - // Length does NOT include the null terminator - // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetActiveUniform.xhtml - name = std::string(glname, length); - binding = glGetUniformLocation(glprogram, glname); + if (index > 0) { + static const GLint NAME_LENGTH = 1024; + GLchar glname[NAME_LENGTH]; + memset(glname, 0, NAME_LENGTH); + GLint length = 0; + glGetActiveUniform(glprogram, index, NAME_LENGTH, &length, &size, &type, glname); + // Length does NOT include the null terminator + // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetActiveUniform.xhtml + name = std::string(glname, length); + binding = glGetUniformLocation(glprogram, name.c_str()); + } } bool isTextureType(GLenum type) { From f1258e44453999926ba5e9ae05c6de20236bd857 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 19 Oct 2018 16:42:45 -0700 Subject: [PATCH 163/276] Add a 'grabbable' option to the new-model dialog --- .../resources/qml/hifi/tablet/NewModelDialog.qml | 10 ++++++++-- scripts/system/edit.js | 15 +++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 10b844c987..0359f429c1 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -116,9 +116,14 @@ Rectangle { Column { id: column2 width: 200 - height: 400 + height: 600 spacing: 10 + CheckBox { + id: grabbable + text: qsTr("Grabbable") + } + CheckBox { id: dynamic text: qsTr("Dynamic") @@ -219,7 +224,8 @@ Rectangle { params: { textInput: modelURL.text, checkBox: dynamic.checked, - comboBox: collisionType.currentIndex + comboBox: collisionType.currentIndex, + grabbable: grabbable.checked } }); } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 686a516504..12b8f4521b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -348,12 +348,12 @@ var toolBar = (function () { if (!properties.grab) { properties.grab = {}; - } - if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && - !(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) { - properties.grab.grabbable = true; - } else { - properties.grab.grabbable = false; + if (Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE) && + !(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) { + properties.grab.grabbable = true; + } else { + properties.grab.grabbable = false; + } } SelectionManager.saveProperties(); @@ -473,6 +473,9 @@ var toolBar = (function () { type: "Model", modelURL: url, shapeType: shapeType, + grab: { + grabbable: result.grabbable + }, dynamic: dynamic, gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 } }); From 070a517423af7e6304f3ddc50564b0e65dd01bd0 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 19 Oct 2018 18:07:03 -0700 Subject: [PATCH 164/276] changed the recenterModel variables to userRecenterModel --- interface/resources/qml/hifi/AvatarApp.qml | 2 +- .../resources/qml/hifi/avatarapp/Settings.qml | 17 ++- interface/src/avatar/MyAvatar.cpp | 108 +++++++++--------- interface/src/avatar/MyAvatar.h | 20 ++-- scripts/system/avatarapp.js | 18 +-- 5 files changed, 80 insertions(+), 85 deletions(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 2d714c0d33..549db20ab1 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -252,7 +252,7 @@ Rectangle { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', collisionsEnabled : settings.avatarCollisionsOn, - recenterModel : settings.avatarRecenterModelOn, + userRecenterModel : settings.avatarRecenterModelOn, animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, collisionSoundUrl : settings.avatarCollisionSoundUrl }; diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index cd71442bca..eb994b86de 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -20,7 +20,7 @@ Rectangle { property real scaleValue: scaleSlider.value / 10 property alias dominantHandIsLeft: leftHandRadioButton.checked property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked - property alias avatarRecenterModelOn: boxy.currentIndex + property alias avatarRecenterModelOn: userModelComboBox.currentIndex property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text @@ -49,8 +49,7 @@ Rectangle { avatarAnimationJSON = settings.animGraphUrl; avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; avatarCollisionSoundUrl = settings.collisionSoundUrl; - print("values " + avatarRecenterModelOn + " " + settings.recenterModel); - avatarRecenterModelOn = settings.recenterModel; + avatarRecenterModelOn = settings.userRecenterModel; visible = true; } @@ -294,22 +293,21 @@ Rectangle { } // TextStyle9 - + RalewaySemiBold { size: 17; Layout.row: 2 Layout.column: 0 - text: "Sitting State" + text: "User Model:" } - ButtonGroup { - id: sitStand - } // sit stand combo box HifiControlsUit.ComboBox { - id: boxy + Layout.row: 2 + Layout.column: 1 + id: userModelComboBox comboBox.textRole: "text" currentIndex: 2 model: ListModel { @@ -325,7 +323,6 @@ Rectangle { console.debug("line 2") } } - } ColumnLayout { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 14829a7b2e..0b58c2c158 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -470,57 +470,58 @@ void MyAvatar::updateSitStandState(float newHeightReading, float dt) { const float SITTING_TIMEOUT = 4.0f; // 4 seconds const float STANDING_TIMEOUT = 0.3333f; // 1/3 second const float SITTING_UPPER_BOUND = 1.52f; - - if (!getIsSitStandStateLocked() && !getIsAway() && qApp->isHMDMode()) { - if (getIsInSittingState()) { - if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we recenter upwards then no longer in sitting state - _sitStandStateTimer += dt; - if (_sitStandStateTimer > STANDING_TIMEOUT) { - _averageUserHeightSensorSpace = newHeightReading; - _tippingPoint = newHeightReading; - setIsInSittingState(false); - } - } else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { - // if we are mis labelled as sitting but we are standing in the real world this will - // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state - _sitStandStateTimer += dt; - if (_sitStandStateTimer > SITTING_TIMEOUT) { - _averageUserHeightSensorSpace = newHeightReading; - _tippingPoint = newHeightReading; - // here we stay in sit state but reset the average height - setIsInSittingState(true); + if (!getIsSitStandStateLocked()) { + if (!getIsAway() && qApp->isHMDMode()) { + if (getIsInSittingState()) { + if (newHeightReading > (STANDING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we recenter upwards then no longer in sitting state + _sitStandStateTimer += dt; + if (_sitStandStateTimer > STANDING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(false); + } + } else if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + // if we are mis labelled as sitting but we are standing in the real world this will + // make sure that a real sit is still recognized so we won't be stuck in sitting unable to change state + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + // here we stay in sit state but reset the average height + setIsInSittingState(true); + } + } else { + // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) + if (_averageUserHeightSensorSpace > SITTING_UPPER_BOUND) { + setIsInSittingState(false); + } else { + // tipping point is average height when sitting. + _tippingPoint = _averageUserHeightSensorSpace; + _sitStandStateTimer = 0.0f; + } } } else { - // sanity check if average height greater than 5ft they are not sitting(or get off your dangerous barstool please) - if (_averageUserHeightSensorSpace > SITTING_UPPER_BOUND) { - setIsInSittingState(false); + // in the standing state + if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { + _sitStandStateTimer += dt; + if (_sitStandStateTimer > SITTING_TIMEOUT) { + _averageUserHeightSensorSpace = newHeightReading; + _tippingPoint = newHeightReading; + setIsInSittingState(true); + } } else { - // tipping point is average height when sitting. - _tippingPoint = _averageUserHeightSensorSpace; + // use the mode height for the tipping point when we are standing. + _tippingPoint = getCurrentStandingHeight(); _sitStandStateTimer = 0.0f; } } } else { - // in the standing state - if (newHeightReading < (SITTING_HEIGHT_MULTIPLE * _tippingPoint)) { - _sitStandStateTimer += dt; - if (_sitStandStateTimer > SITTING_TIMEOUT) { - _averageUserHeightSensorSpace = newHeightReading; - _tippingPoint = newHeightReading; - setIsInSittingState(true); - } - } else { - // use the mode height for the tipping point when we are standing. - _tippingPoint = getCurrentStandingHeight(); - _sitStandStateTimer = 0.0f; - } + //if you are away then reset the average and set state to standing. + _averageUserHeightSensorSpace = _userHeight.get(); + _tippingPoint = _userHeight.get(); + setIsInSittingState(false); } - } else { - // if you are away then reset the average and set state to standing. - _averageUserHeightSensorSpace = _userHeight.get(); - _tippingPoint = _userHeight.get(); - setIsInSittingState(false); } } @@ -533,7 +534,7 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); - //qCDebug(interfaceapp) << " the sit state is " << _isInSittingState.get() << " the lock is " << _lockSitStandState.get(); + qCDebug(interfaceapp) << " the sit state is " << _isInSittingState.get() << " the lock is " << _lockSitStandState.get(); // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter @@ -3867,8 +3868,8 @@ bool MyAvatar::getIsInSittingState() const { return _isInSittingState.get(); } -MyAvatar::SitStandModelType MyAvatar::getRecenterModel() const { - return _recenterModel.get(); +MyAvatar::SitStandModelType MyAvatar::getUserRecenterModel() const { + return _userRecenterModel.get(); } bool MyAvatar::getIsSitStandStateLocked() const { @@ -3907,25 +3908,25 @@ void MyAvatar::setIsInSittingState(bool isSitting) { setCenterOfGravityModelEnabled(true); } setSitStandStateChange(true); - - emit sittingEnabledChanged(isSitting); } -void MyAvatar::setRecenterModel(MyAvatar::SitStandModelType modelName) { +void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { + + _userRecenterModel.set(modelName); - _recenterModel.set(modelName); - //int temp = 0; - qCDebug(interfaceapp) << "recenter property changed " << modelName; switch (modelName) { case SitStandModelType::ForceSit: + setHMDLeanRecenterEnabled(true); setIsInSittingState(true); setIsSitStandStateLocked(true); break; case SitStandModelType::ForceStand: + setHMDLeanRecenterEnabled(true); setIsInSittingState(false); setIsSitStandStateLocked(true); break; case SitStandModelType::Auto: + setHMDLeanRecenterEnabled(true); setIsInSittingState(false); setIsSitStandStateLocked(false); break; @@ -3936,7 +3937,7 @@ void MyAvatar::setRecenterModel(MyAvatar::SitStandModelType modelName) { break; } qCDebug(interfaceapp) << "recenter property changed " << modelName << " sit " << _isInSittingState.get() << " lock " << _lockSitStandState.get(); - emit recenterModelChanged((int)modelName); + emit userRecenterModelChanged((int)modelName); } void MyAvatar::setIsSitStandStateLocked(bool isLocked) { @@ -3949,7 +3950,6 @@ void MyAvatar::setIsSitStandStateLocked(bool isLocked) { // always start the auto transition mode in standing state. setIsInSittingState(false); } - emit sitStandStateLockEnabledChanged(isLocked); } void MyAvatar::setWalkSpeed(float value) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c59bdcd66d..691ab80530 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -143,7 +143,7 @@ class MyAvatar : public Avatar { * @property {number} walkBackwardSpeed * @property {number} sprintSpeed * @property {number} isInSittingState - * @property {number} recenterModel + * @property {number} userRecenterModel * * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the * registration point of the 3D model. @@ -245,7 +245,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float walkBackwardSpeed READ getWalkBackwardSpeed WRITE setWalkBackwardSpeed); Q_PROPERTY(float sprintSpeed READ getSprintSpeed WRITE setSprintSpeed); Q_PROPERTY(bool isInSittingState READ getIsInSittingState WRITE setIsInSittingState); - Q_PROPERTY(MyAvatar::SitStandModelType recenterModel READ getRecenterModel WRITE setRecenterModel); + Q_PROPERTY(MyAvatar::SitStandModelType userRecenterModel READ getUserRecenterModel WRITE setUserRecenterModel); Q_PROPERTY(bool isSitStandStateLocked READ getIsSitStandStateLocked WRITE setIsSitStandStateLocked); const QString DOMINANT_LEFT_HAND = "left"; @@ -1117,8 +1117,8 @@ public: bool getIsInWalkingState() const; void setIsInSittingState(bool isSitting); bool getIsInSittingState() const; - void setRecenterModel(MyAvatar::SitStandModelType modelName); - MyAvatar::SitStandModelType getRecenterModel() const; + void setUserRecenterModel(MyAvatar::SitStandModelType modelName); + MyAvatar::SitStandModelType getUserRecenterModel() const; void setIsSitStandStateLocked(bool isLocked); bool getIsSitStandStateLocked() const; void setWalkSpeed(float value); @@ -1545,11 +1545,11 @@ signals: /**jsdoc * Triggered when the recenter model is changed - * @function MyAvatar.recenterModelChanged - * @param {int} modeltype - * @ + * @function MyAvatar.userRecenterModelChanged + * @param {int} userRecenteringModeltype + * @returns {Signal} */ - void recenterModelChanged(int modelName); + void userRecenterModelChanged(int modelName); /**jsdoc * Triggered when the sit state is enabled or disabled @@ -1850,8 +1850,6 @@ private: void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); - - // height of user in sensor space, when standing erect. ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; float _averageUserHeightSensorSpace { _userHeight.get() }; @@ -1865,7 +1863,7 @@ private: float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR }; bool _isInWalkingState { false }; ThreadSafeValueCache _isInSittingState { false }; - ThreadSafeValueCache _recenterModel { MyAvatar::SitStandModelType::Auto }; + ThreadSafeValueCache _userRecenterModel { MyAvatar::SitStandModelType::Auto }; float _sitStandStateTimer { 0.0f }; float _squatTimer { 0.0f }; float _tippingPoint { _userHeight.get() }; diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index d64454c534..b30f87c944 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -64,7 +64,7 @@ function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), collisionsEnabled : MyAvatar.getCollisionsEnabled(), - recenterModel: MyAvatar.recenterModel, + userRecenterModel: MyAvatar.userRecenterModel, collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), @@ -137,11 +137,11 @@ function onCollisionsEnabledChanged(enabled) { } } -function onRecenterModelChanged(modelName) { - if (currentAvatarSettings.recenterModel !== modelName) { - currentAvatarSettings.recenterModel = modelName; - print("emit recenter model changed"); - sendToQml({ 'method': 'settingChanged', 'name': 'recenterModel', 'value': modelName }) +function onUserRecenterModelChanged(modelName) { + if (currentAvatarSettings.userRecenterModel !== modelName) { + currentAvatarSettings.userRecenterModel = modelName; + print("emit user recenter model changed"); + sendToQml({ 'method': 'settingChanged', 'name': 'userRecenterModel', 'value': modelName }) } } @@ -322,7 +322,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See currentAvatar.avatarScale = message.avatarScale; MyAvatar.setDominantHand(message.settings.dominantHand); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); - MyAvatar.recenterModel = message.settings.recenterModel; + MyAvatar.userRecenterModel = message.settings.userRecenterModel; MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); settings = getMyAvatarSettings(); @@ -515,7 +515,7 @@ function off() { MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); - MyAvatar.recenterModelChanged.disconnect(onRecenterModelChanged); + MyAvatar.userRecenterModelChanged.disconnect(onUserRecenterModelChanged); MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); @@ -530,7 +530,7 @@ function on() { MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); - MyAvatar.recenterModelChanged.connect(onRecenterModelChanged); + MyAvatar.userRecenterModelChanged.connect(onUserRecenterModelChanged); MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); From 55daeb11cdcdb007ec7cfcabbc8eac18d46a4270 Mon Sep 17 00:00:00 2001 From: amantley Date: Fri, 19 Oct 2018 18:43:02 -0700 Subject: [PATCH 165/276] moved the radio buttons on the avatar settings to even them out better. will be corrected I'm sure --- interface/resources/qml/hifi/avatarapp/Settings.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index eb994b86de..dbf38eb5fc 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -32,7 +32,7 @@ Rectangle { scaleSlider.notify = false; scaleSlider.value = Math.round(avatarScale * 10); - scaleSlider.notify = true;; + scaleSlider.notify = true; if (settings.dominantHand === 'left') { leftHandRadioButton.checked = true; @@ -191,7 +191,7 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right - rows: 2 + rows: 3 rowSpacing: 25 columns: 3 @@ -214,7 +214,7 @@ Rectangle { Layout.row: 0 Layout.column: 1 - Layout.leftMargin: -40 + Layout.leftMargin: 20 ButtonGroup.group: leftRight checked: true @@ -231,7 +231,7 @@ Rectangle { Layout.row: 0 Layout.column: 2 - Layout.rightMargin: 20 + Layout.rightMargin: -20 ButtonGroup.group: leftRight @@ -260,7 +260,7 @@ Rectangle { Layout.row: 1 Layout.column: 1 - Layout.leftMargin: -40 + Layout.leftMargin: 20 ButtonGroup.group: onOff colorScheme: hifi.colorSchemes.light @@ -281,7 +281,7 @@ Rectangle { Layout.row: 1 Layout.column: 2 - Layout.rightMargin: 20 + Layout.rightMargin: -20 ButtonGroup.group: onOff colorScheme: hifi.colorSchemes.light From a427ddb23543dac8e9cf1a111b24c9c6267ac797 Mon Sep 17 00:00:00 2001 From: amantley Date: Sat, 20 Oct 2018 09:16:05 -0700 Subject: [PATCH 166/276] removed extraneous print statements --- interface/resources/qml/hifi/avatarapp/Settings.qml | 4 ---- interface/src/avatar/MyAvatar.cpp | 2 -- 2 files changed, 6 deletions(-) diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index dbf38eb5fc..b7083bda13 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -318,10 +318,6 @@ Rectangle { ListElement { text: "Disable Recentering"; color: "Red" } } width: 200 - onCurrentIndexChanged: { - console.debug(cbItems.get(currentIndex).text + ", " + cbItems.get(currentIndex).color) - console.debug("line 2") - } } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0b58c2c158..c8d33a770e 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -534,7 +534,6 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); - qCDebug(interfaceapp) << " the sit state is " << _isInSittingState.get() << " the lock is " << _lockSitStandState.get(); // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter @@ -3936,7 +3935,6 @@ void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { setIsSitStandStateLocked(false); break; } - qCDebug(interfaceapp) << "recenter property changed " << modelName << " sit " << _isInSittingState.get() << " lock " << _lockSitStandState.get(); emit userRecenterModelChanged((int)modelName); } From 9d13902cd3c9f89b3f9bf2e3f1ad9e35fb5326e7 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Sat, 20 Oct 2018 11:26:12 -0700 Subject: [PATCH 167/276] Improve paramater names in model dialog window --- interface/resources/qml/hifi/tablet/NewModelDialog.qml | 6 +++--- scripts/system/edit.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 0359f429c1..553a4fd59f 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -222,9 +222,9 @@ Rectangle { newModelDialog.sendToScript({ method: "newModelDialogAdd", params: { - textInput: modelURL.text, - checkBox: dynamic.checked, - comboBox: collisionType.currentIndex, + url: modelURL.text, + dynamic: dynamic.checked, + collisionShapeIndex: collisionType.currentIndex, grabbable: grabbable.checked } }); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 12b8f4521b..1720cb8278 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -442,9 +442,9 @@ var toolBar = (function () { function handleNewModelDialogResult(result) { if (result) { - var url = result.textInput; + var url = result.url; var shapeType; - switch (result.comboBox) { + switch (result.collisionShapeIndex) { case SHAPE_TYPE_SIMPLE_HULL: shapeType = "simple-hull"; break; @@ -464,7 +464,7 @@ var toolBar = (function () { shapeType = "none"; } - var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; + var dynamic = result.dynamic !== null ? result.dynamic : DYNAMIC_DEFAULT; if (shapeType === "static-mesh" && dynamic) { // The prompt should prevent this case print("Error: model cannot be both static mesh and dynamic. This should never happen."); From 5efb889de53ea3fd5d4375a05db885ce4bf02787 Mon Sep 17 00:00:00 2001 From: amantley Date: Sat, 20 Oct 2018 13:42:17 -0700 Subject: [PATCH 168/276] removed unused variables --- interface/src/avatar/MyAvatar.cpp | 44 ++++++++++++++----------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c8d33a770e..94a6c6f514 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3606,12 +3606,9 @@ glm::vec3 MyAvatar::computeCounterBalance() { glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead; // find the height of the hips - const float UPPER_LEG_FRACTION = 0.3333f; glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z)); float headMinusHipXz = glm::length(xzDiff); float headHipDefault = glm::length(tposeHead - tposeHips); - float hipFootDefault = tposeHips.y - tposeRightFoot.y; - float sitSquatThreshold = tposeHips.y - (UPPER_LEG_FRACTION * hipFootDefault); float hipHeight = 0.0f; if (headHipDefault > headMinusHipXz) { hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz)); @@ -3914,26 +3911,26 @@ void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { _userRecenterModel.set(modelName); switch (modelName) { - case SitStandModelType::ForceSit: - setHMDLeanRecenterEnabled(true); - setIsInSittingState(true); - setIsSitStandStateLocked(true); - break; - case SitStandModelType::ForceStand: - setHMDLeanRecenterEnabled(true); - setIsInSittingState(false); - setIsSitStandStateLocked(true); - break; - case SitStandModelType::Auto: - setHMDLeanRecenterEnabled(true); - setIsInSittingState(false); - setIsSitStandStateLocked(false); - break; - case SitStandModelType::DisableHMDLean: - setHMDLeanRecenterEnabled(false); - setIsInSittingState(false); - setIsSitStandStateLocked(false); - break; + case MyAvatar::SitStandModelType::ForceSit: + setHMDLeanRecenterEnabled(true); + setIsInSittingState(true); + setIsSitStandStateLocked(true); + break; + case MyAvatar::SitStandModelType::ForceStand: + setHMDLeanRecenterEnabled(true); + setIsInSittingState(false); + setIsSitStandStateLocked(true); + break; + case MyAvatar::SitStandModelType::Auto: + setHMDLeanRecenterEnabled(true); + setIsInSittingState(false); + setIsSitStandStateLocked(false); + break; + case MyAvatar::SitStandModelType::DisableHMDLean: + setHMDLeanRecenterEnabled(false); + setIsInSittingState(false); + setIsSitStandStateLocked(false); + break; } emit userRecenterModelChanged((int)modelName); } @@ -4190,7 +4187,6 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co const float CYLINDER_TOP = 0.1f; const float CYLINDER_BOTTOM = -1.5f; const float SITTING_BOTTOM = -0.02f; - const int SQUATTY_COUNT_THRESHOLD = 1800; glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix); bool returnValue = false; From f8d67d124fc8b80623f4557eeffeb944314f1de6 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sat, 20 Oct 2018 15:22:25 -0700 Subject: [PATCH 169/276] Hold flickering fixed and refactor --- interface/src/avatar/AvatarManager.cpp | 14 +++--- interface/src/avatar/MyAvatar.cpp | 1 - libraries/animation/src/Rig.cpp | 2 +- .../src/avatars-renderer/Avatar.cpp | 45 +++++-------------- .../src/avatars-renderer/Avatar.h | 10 ++--- libraries/avatars/src/AvatarData.cpp | 29 +++++------- libraries/avatars/src/AvatarData.h | 15 +++++-- 7 files changed, 46 insertions(+), 70 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index dd56c03d26..bd3fba9a69 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -79,14 +79,12 @@ AvatarManager::AvatarManager(QObject* parent) : } }); - const float AVATAR_TRANSIT_TRIGGER_DISTANCE = 1.0f; - const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing - const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f; // Based on testing - _transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT; - _transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE; + _transitConfig._minTriggerDistance = AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE; + _transitConfig._maxTriggerDistance = AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE; _transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER; - _transitConfig._isDistanceBased = true; + _transitConfig._isDistanceBased = AVATAR_TRANSIT_DISTANCE_BASED; + _transitConfig._abortDistance = AVATAR_TRANSIT_ABORT_DISTANCE; } AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { @@ -169,7 +167,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()"); - AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig); + AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _myAvatar->getSensorToWorldScale(), _transitConfig); handleTransitAnimations(status); _myAvatar->update(deltaTime); @@ -300,7 +298,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } - auto transitStatus = avatar->_transit.update(deltaTime, avatar->_lastPosition, _transitConfig); + auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig); if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) { avatar->_transit.reset(); avatar->setIsNewAvatar(false); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b347963cf1..ca283616a5 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -929,7 +929,6 @@ void MyAvatar::updateSensorToWorldMatrix() { updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache); if (hasSensorToWorldScaleChanged) { - setTransitScale(sensorToWorldScale); emit sensorToWorldScaleChanged(sensorToWorldScale); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 35f076c78d..31c90a3070 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1138,7 +1138,7 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons } } if (_sendNetworkNode) { - for (auto i = 0; i < _networkPoseSet._relativePoses.size(); i++) { + for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) { _networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha)); } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 705ebc0b13..c140f1bede 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -115,10 +115,8 @@ void Avatar::setShowNamesAboveHeads(bool show) { AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition); - const float TRANSIT_TRIGGER_MAX_DISTANCE = 30.0f; - float scaledMaxTransitDistance = TRANSIT_TRIGGER_MAX_DISTANCE * _scale; - if (oneFrameDistance > config._triggerDistance) { - if (oneFrameDistance < scaledMaxTransitDistance) { + if (oneFrameDistance > (config._minTriggerDistance * _scale)) { + if (oneFrameDistance < (config._maxTriggerDistance * _scale)) { start(deltaTime, _lastPosition, avatarPosition, config); } else { _lastPosition = avatarPosition; @@ -128,9 +126,7 @@ AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& av _lastPosition = avatarPosition; _status = updatePosition(deltaTime); - const float SETTLE_ABORT_DISTANCE = 0.1f; - float scaledSettleAbortDistance = SETTLE_ABORT_DISTANCE * _scale; - if (_isActive && oneFrameDistance > scaledSettleAbortDistance && _status == Status::POST_TRANSIT) { + if (_isActive && oneFrameDistance > (config._abortDistance * _scale) && _status == Status::POST_TRANSIT) { reset(); _status = Status::ENDED; } @@ -150,14 +146,10 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const _totalDistance = glm::length(_transitLine); _easeType = config._easeType; - const float REFERENCE_FRAMES_PER_SECOND = 30.0f; - const float PRE_TRANSIT_FRAME_COUNT = 10.0f; - const float POST_TRANSIT_FRAME_COUNT = 27.0f; - - _preTransitTime = PRE_TRANSIT_FRAME_COUNT / REFERENCE_FRAMES_PER_SECOND; - _postTransitTime = POST_TRANSIT_FRAME_COUNT / REFERENCE_FRAMES_PER_SECOND; + _preTransitTime = AVATAR_PRE_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND; + _postTransitTime = AVATAR_POST_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND; int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance; - _transitTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND; + _transitTime = (float)transitFrames / AVATAR_TRANSIT_FRAMES_PER_SECOND; _totalTime = _transitTime + _preTransitTime + _postTransitTime; _currentTime = _isActive ? _preTransitTime : 0.0f; _isActive = true; @@ -554,13 +546,10 @@ void Avatar::relayJointDataToChildren() { void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); - - if (_transit.isActive()) { - _globalPosition = _transit.getCurrentPosition(); - _globalPositionChanged = usecTimestampNow(); - if (!hasParent()) { - setLocalPosition(_transit.getCurrentPosition()); - } + + _globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition; + if (!hasParent()) { + setLocalPosition(_globalPosition); } _simulationRate.increment(); @@ -1994,22 +1983,12 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { } } -AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) { +AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config) { std::lock_guard lock(_transitLock); + _transit.setScale(avatarScale); return _transit.update(deltaTime, avatarPosition, config); } -void Avatar::setTransitScale(float scale) { - std::lock_guard lock(_transitLock); - _transit.setScale(scale); -} - -void Avatar::overrideNextPacketPositionData(const glm::vec3& position) { - std::lock_guard lock(_transitLock); - _overrideGlobalPosition = true; - _globalPositionOverride = position; -} - void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].push(material); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 2995c0f7b4..3df923b048 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -76,7 +76,9 @@ public: int _totalFrames { 0 }; float _framesPerMeter { 0.0f }; bool _isDistanceBased { false }; - float _triggerDistance { 0 }; + float _minTriggerDistance { 0.0f }; + float _maxTriggerDistance { 0.0f }; + float _abortDistance{ 0.0f }; EaseType _easeType { EaseType::EASE_OUT }; }; @@ -434,11 +436,7 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel() override; std::shared_ptr getTransit() { return std::make_shared(_transit); }; - - AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config); - void setTransitScale(float scale); - - void overrideNextPacketPositionData(const glm::vec3& position); + AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config); signals: void targetScaleChanged(float targetScale); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a22cc4a1d3..4ff3c0192d 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -371,12 +371,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasAvatarGlobalPosition) { auto startSection = destinationBuffer; - if (_overrideGlobalPosition) { - AVATAR_MEMCPY(_globalPositionOverride); - } else { - AVATAR_MEMCPY(_globalPosition); - } - + AVATAR_MEMCPY(_globalPosition); int numBytes = destinationBuffer - startSection; @@ -894,20 +889,22 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { offset = glm::vec3(row * SPACE_BETWEEN_AVATARS, 0.0f, col * SPACE_BETWEEN_AVATARS); } - auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; - if (_globalPosition != newValue) { - _lastPosition = _globalPosition = newValue; + _serverPosition = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; + auto oneStepDistance = glm::length(_globalPosition - _serverPosition); + if (oneStepDistance <= AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE || oneStepDistance >= AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE) { + _globalPosition = _serverPosition; + // if we don't have a parent, make sure to also set our local position + if (!hasParent()) { + setLocalPosition(_serverPosition); + } + } + if (_globalPosition != _serverPosition) { _globalPositionChanged = now; } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); int numBytesRead = sourceBuffer - startSection; _globalPositionRate.increment(numBytesRead); _globalPositionUpdateRate.increment(); - - // if we don't have a parent, make sure to also set our local position - if (!hasParent()) { - setLocalPosition(newValue); - } } if (hasAvatarBoundingBox) { @@ -2110,10 +2107,6 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) { } } - if (_overrideGlobalPosition) { - _overrideGlobalPosition = false; - } - doneEncoding(cullSmallData); static AvatarDataSequenceNumber sequenceNumber = 0; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index dcdaa70ad7..fcbe05ca52 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -327,6 +327,17 @@ const float AVATAR_DISTANCE_LEVEL_5 = 200.0f; // meters // This is the start location in the Sandbox (xyz: 6270, 211, 6000). const glm::vec3 START_LOCATION(6270, 211, 6000); +// Avatar Transit Constants +const float AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE = 1.0f; +const float AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE = 30.0f; +const int AVATAR_TRANSIT_FRAME_COUNT = 11; +const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f; +const float AVATAR_TRANSIT_ABORT_DISTANCE = 0.1f; +const bool AVATAR_TRANSIT_DISTANCE_BASED = true; +const float AVATAR_TRANSIT_FRAMES_PER_SECOND = 30.0f; +const float AVATAR_PRE_TRANSIT_FRAME_COUNT = 10.0f; +const float AVATAR_POST_TRANSIT_FRAME_COUNT = 27.0f; + enum KeyState { NO_KEY_DOWN = 0, INSERT_KEY_DOWN, @@ -1378,9 +1389,7 @@ protected: // where Entities are located. This is currently only used by the mixer to decide how often to send // updates about one avatar to another. glm::vec3 _globalPosition { 0, 0, 0 }; - glm::vec3 _lastPosition { 0, 0, 0 }; - glm::vec3 _globalPositionOverride { 0, 0, 0 }; - bool _overrideGlobalPosition { false }; + glm::vec3 _serverPosition { 0, 0, 0 }; quint64 _globalPositionChanged { 0 }; quint64 _avatarBoundingBoxChanged { 0 }; From eb3db9a5715ad53c0344bee64647e67aa0de8eac Mon Sep 17 00:00:00 2001 From: amantley Date: Sat, 20 Oct 2018 16:13:41 -0700 Subject: [PATCH 170/276] added default to case statement --- interface/src/avatar/MyAvatar.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b2146fb14d..dd40a748af 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3970,6 +3970,8 @@ void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { setIsInSittingState(false); setIsSitStandStateLocked(false); break; + default: + break; } emit userRecenterModelChanged((int)modelName); } From d008bfa523e6f61879290b09fd1d7a8cc61b8037 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 22 Oct 2018 11:29:36 -0700 Subject: [PATCH 171/276] partial requested code changes --- .../controllers/controllerModules/webSurfaceLaserInput.js | 2 -- scripts/system/interstitialPage.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 9f4133674a..c2949bebe4 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -90,7 +90,6 @@ Script.include("/~/system/libraries/controllers.js"); action: 'add', id: objectID }; - print("ignoreing entity " + entityIndex); Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)); this.ignoredObjects.push(objectID); } @@ -99,7 +98,6 @@ Script.include("/~/system/libraries/controllers.js"); this.restoreIgnoredObjects = function() { for (var index = 0; index < this.ignoredObjects.length; index++) { - print("removing"); var data = { action: 'remove', id: this.ignoredObjects[index] diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index a611f386ff..b29347872a 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -241,7 +241,7 @@ } function lerp(a, b, t) { - return (((1 - t) * a) + (t * b)); + return ((1 - t) * a + t * b); } function resetValues() { From ddbe02dd4e9b5993027a86ab72e9a405d80c5056 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 22 Oct 2018 11:56:47 -0700 Subject: [PATCH 172/276] CR changes and fix extra info toggle --- scripts/system/html/css/edit-style.css | 15 +++++--- scripts/system/html/entityList.html | 6 ++-- scripts/system/html/js/entityList.js | 48 +++++++++++++------------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 1b0094cfb7..13952c5e38 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1063,10 +1063,10 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { .multiselect { position: relative; } -.selectBox { +.select-box { position: absolute; } -.selectBox select { +.select-box select { font-family: FiraSans-SemiBold; font-size: 15px; color: #afafaf; @@ -1076,7 +1076,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { width: 107px; text-align-last: center; } -.overSelect { +.over-select { position: absolute; left: 0; right: 0; @@ -1084,7 +1084,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { bottom: 0; } -#filter-type-selectBox select { +#filter-type-select-box select { border-radius: 14.5px; } #filter-type-checkboxes { @@ -1158,6 +1158,13 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { #filter-radius-and-unit label { margin-left: 2px; } +#filter-radius-and-unit span { + position: relative; + top: 25px; + right: 9px; + z-index: 2; + font-style: italic; +} #filter-radius-and-unit input { width: 120px; border-radius: 14.5px; diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 434f8a5f87..187aa70347 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -31,11 +31,11 @@

-
+
-
+
@@ -47,7 +47,7 @@
- +
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 66ad08e27c..2d248e48e6 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -131,7 +131,7 @@ function loaded() { elToggleLocked = document.getElementById("locked"); elToggleVisible = document.getElementById("visible"); elDelete = document.getElementById("delete"); - elFilterTypeSelectBox = document.getElementById("filter-type-selectBox"); + elFilterTypeSelectBox = document.getElementById("filter-type-select-box"); elFilterTypeText = document.getElementById("filter-type-text"); elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes"); elFilterSearch = document.getElementById("filter-search"); @@ -155,31 +155,31 @@ function loaded() { document.getElementById("entity-url").onclick = function() { setSortColumn('url'); }; - document.getElementById("entity-locked").onclick = function () { + document.getElementById("entity-locked").onclick = function() { setSortColumn('locked'); }; - document.getElementById("entity-visible").onclick = function () { + document.getElementById("entity-visible").onclick = function() { setSortColumn('visible'); }; - document.getElementById("entity-verticesCount").onclick = function () { + document.getElementById("entity-verticesCount").onclick = function() { setSortColumn('verticesCount'); }; - document.getElementById("entity-texturesCount").onclick = function () { + document.getElementById("entity-texturesCount").onclick = function() { setSortColumn('texturesCount'); }; - document.getElementById("entity-texturesSize").onclick = function () { + document.getElementById("entity-texturesSize").onclick = function() { setSortColumn('texturesSize'); }; - document.getElementById("entity-hasTransparent").onclick = function () { + document.getElementById("entity-hasTransparent").onclick = function() { setSortColumn('hasTransparent'); }; - document.getElementById("entity-isBaked").onclick = function () { + document.getElementById("entity-isBaked").onclick = function() { setSortColumn('isBaked'); }; - document.getElementById("entity-drawCalls").onclick = function () { + document.getElementById("entity-drawCalls").onclick = function() { setSortColumn('drawCalls'); }; - document.getElementById("entity-hasScript").onclick = function () { + document.getElementById("entity-hasScript").onclick = function() { setSortColumn('hasScript'); }; elRefresh.onclick = function() { @@ -203,12 +203,9 @@ function loaded() { elFilterSearch.onkeyup = refreshEntityList; elFilterSearch.onsearch = refreshEntityList; elFilterInView.onclick = toggleFilterInView; + elFilterRadius.onkeyup = onRadiusChange; elFilterRadius.onchange = onRadiusChange; - elFilterRadius.oninput = function(event) { - if (event.target.value.length > MAX_LENGTH_RADIUS) { - event.target.value = event.target.value.slice(0, MAX_LENGTH_RADIUS); - } - } + elInfoToggle.onclick = toggleInfo; // create filter type dropdown checkboxes with label and icon for each type elFilterTypeSelectBox.onclick = toggleTypeDropdown; @@ -225,9 +222,10 @@ function loaded() { let elInput = document.createElement('input'); elInput.setAttribute("type", "checkbox"); elInput.setAttribute("id", typeFilterID); + elInput.setAttribute("filterType", type); elInput.checked = true; // all types are checked initially - toggleTypeFilter(type, false); // add all types to the initial types filter - elInput.onclick = onToggleTypeFilter(type); + toggleTypeFilter(elInput, false); // add all types to the initial types filter + elInput.onclick = onToggleTypeFilter; elDiv.appendChild(elInput); elLabel.insertBefore(elSpan, elLabel.childNodes[0]); elDiv.appendChild(elLabel); @@ -642,6 +640,7 @@ function loaded() { } function onRadiusChange() { + elFilterRadius.value = elFilterRadius.value.replace(/[^0-9]/g, ''); elFilterRadius.value = Math.max(elFilterRadius.value, 0); EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value })); refreshEntities(); @@ -655,11 +654,14 @@ function loaded() { elFilterTypeCheckboxes.style.display = isTypeDropdownVisible() ? "none" : "block"; } - function toggleTypeFilter(type, refresh) { + function toggleTypeFilter(elInput, refresh) { + let type = elInput.getAttribute("filterType"); + let typeChecked = elInput.checked; + let typeFilterIndex = typeFilters.indexOf(type); - if (typeFilterIndex > -1) { + if (!typeChecked && typeFilterIndex > -1) { typeFilters.splice(typeFilterIndex, 1); - } else { + } else if (typeChecked && typeFilterIndex === -1) { typeFilters.push(type); } @@ -676,10 +678,8 @@ function loaded() { } } - function onToggleTypeFilter(type) { - return function() { - toggleTypeFilter(type, true); - }; + function onToggleTypeFilter(event) { + toggleTypeFilter(this, true); } function onBodyClick(event) { From 1ce1902fa8f9d2dd0571d651e0094abe425a5445 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 22 Oct 2018 12:49:24 -0700 Subject: [PATCH 173/276] remove magic numbers --- scripts/system/interstitialPage.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index b29347872a..670d21c7a7 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -459,13 +459,14 @@ } } + var MAX_TEXTURE_STABILITY_COUNT = 30; + var INTERVAL_PROGRESS = 0.04; function update() { var renderStats = Render.getConfig("Stats"); var physicsEnabled = Window.isPhysicsEnabled(); var thisInterval = Date.now(); var deltaTime = (thisInterval - lastInterval); lastInterval = thisInterval; - var deltaTimeMS = deltaTime / 1000; var domainLoadingProgressPercentage = Window.domainLoadingProgress(); var progress = ((TOTAL_LOADING_PROGRESS * 0.4) * domainLoadingProgressPercentage); @@ -485,7 +486,7 @@ textureMemSizeAtLastCheck = textureResourceGPUMemSize; - if (textureMemSizeStabilityCount >= 30) { + if (textureMemSizeStabilityCount >= MAX_TEXTURE_STABILITY_COUNT) { if (textureResourceGPUMemSize > 0) { var gpuPercantage = (TOTAL_LOADING_PROGRESS * 0.6) * (texturePopulatedGPUMemSize / textureResourceGPUMemSize); @@ -501,11 +502,7 @@ target = TOTAL_LOADING_PROGRESS; } - if (deltaTime > 1.0) { - deltaTimeMS = 0.02; - } - - currentProgress = lerp(currentProgress, target, (deltaTimeMS * 2.0)); + currentProgress = lerp(currentProgress, target, INTERVAL_PROGRESS); var properties = { localPosition: { x: (1.85 - (currentProgress / 2) - (-0.029 * (currentProgress / TOTAL_LOADING_PROGRESS))), y: -0.935, z: 0.0 }, dimensions: { From 35c1241c9886909cad86dce0b8079245806b5bee Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 23 Oct 2018 09:08:38 +1300 Subject: [PATCH 174/276] Fix tablet highlighting not working if start up in HMD --- .../system/controllers/controllerModules/nearTabletHighlight.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/controllers/controllerModules/nearTabletHighlight.js b/scripts/system/controllers/controllerModules/nearTabletHighlight.js index 2b02bf3aed..c24464ab38 100644 --- a/scripts/system/controllers/controllerModules/nearTabletHighlight.js +++ b/scripts/system/controllers/controllerModules/nearTabletHighlight.js @@ -109,6 +109,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); } HMD.displayModeChanged.connect(onDisplayModeChanged); HMD.mountedChanged.connect(onDisplayModeChanged); + onDisplayModeChanged(); function cleanUp() { disableDispatcherModule("LeftNearTabletHighlight"); From 9cc2add4a045396bd8ff2bce175040ea2f93c6f3 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 18 Oct 2018 12:35:39 -0700 Subject: [PATCH 175/276] Add copy/paste functionality to edit.js --- .../resources/controllers/keyboardMouse.json | 2 +- scripts/system/edit.js | 239 +++++++++++++++++- 2 files changed, 232 insertions(+), 9 deletions(-) diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index 80933a2489..2ad07911c6 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -133,7 +133,7 @@ { "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" }, { "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" }, { "from": "Keyboard.Shift", "when": ["!Keyboard.Left", "!Keyboard.Right"], "to": "Actions.SPRINT" }, - { "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" }, + { "from": "Keyboard.C", "when": "!Keyboard.Control", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" }, diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 1720cb8278..f172df7bae 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1933,14 +1933,6 @@ function gridKey(value) { } } } -var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); -mapping.from([Controller.Hardware.Keyboard.Delete]).when([!Controller.Hardware.Application.PlatformMac]).to(deleteKey); -mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware.Application.PlatformMac]).to(deleteKey); -mapping.from([Controller.Hardware.Keyboard.D]).when([Controller.Hardware.Keyboard.Control]).to(deselectKey); -mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); -mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); -mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); - function recursiveAdd(newParentID, parentData) { if (parentData.children !== undefined) { var children = parentData.children; @@ -2398,6 +2390,213 @@ var PropertiesTool = function (opts) { return that; }; +function addChildrenEntities(parentEntityID, entityList) { + var children = Entities.getChildrenIDs(parentEntityID); + for (var i = 0; i < children.length; i++) { + var childID = children[i]; + if (entityList.indexOf(childID) < 0) { + entityList.push(childID); + } + addChildrenEntities(childID, entityList); + } +} + +// Return true if the given entity with `properties` is being grabbed by an avatar. +// This is mostly a heuristic - there is no perfect way to know if an entity is being +// grabbed. +function nonDynamicEntityIsBeingGrabbedByAvatar(properties) { + if (properties.dynamic || Uuid.isNull(properties.parentID)) { + return false; + } + + var avatar = AvatarList.getAvatar(properties.parentID); + if (Uuid.isNull(avatar.sessionUUID)) { + return false; + } + + var grabJointNames = [ + 'RightHand', 'LeftHand', + '_CONTROLLER_RIGHTHAND', '_CONTROLLER_LEFTHAND', + '_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND', '_CAMERA_RELATIVE_CONTROLLER_LEFTHAND']; + + for (var i = 0; i < grabJointNames.length; ++i) { + if (avatar.getJointIndex(grabJointNames[i]) === properties.parentJointIndex) { + return true; + } + } + + return false; +} + + +// entityProperites - array of entity property objects +function createEntities(entityProperties) { + var entitiesToCreate = []; + var createdEntityIDs = []; + var createdChildrenWithOldParents = []; + var originalEntityToNewEntityID = []; + + SelectionManager.saveProperties(); + + for (var i = 0, len = entityProperties.length; i < len; ++i) { + var properties = entityProperties[i]; + if (properties.parentID in originalEntityToNewEntityID) { + properties.parentID = originalEntityToNewEntityID[properties.parentID]; + } else { + delete properties.parentID; + } + + delete properties.actionData; + var newEntityID = Entities.addEntity(properties); + + if (newEntityID) { + createdEntityIDs.push({ + entityID: newEntityID, + properties: properties + }); + if (properties.parentID !== Uuid.NULL) { + createdChildrenWithOldParents[newEntityID] = properties.parentID; + } + originalEntityToNewEntityID[properties.id] = newEntityID; + properties.id = newEntityID; + } + } + + return createdEntityIDs; +} + +function copySelectedEntities() { + copyEntities(selectionManager.selections); +} + +var entityClipboard = { + entities: {}, // Map of id -> properties for copied entities + position: { x: 0, y: 0, z: 0 }, + dimensions: { x: 0, y: 0, z: 0 }, +}; + +function copyEntities(entityIDs) { + var entityProperties = Entities.getMultipleEntityProperties(entityIDs); + var entities = {}; + entityProperties.forEach(function(props) { + entities[props.id] = props; + }); + + function appendChildren(entityID, entities) { + var childrenIDs = Entities.getChildrenIDs(entityID); + for (var i = 0; i < childrenIDs.length; ++i) { + var id = childrenIDs[i]; + if (!(id in entities)) { + entities[id] = Entities.getEntityProperties(id); + appendChildren(id, entities); + } + } + } + + var len = entityProperties.length; + for (var i = 0; i < len; ++i) { + appendChildren(entityProperties[i].id, entities); + } + + for (var id in entities) { + var parentID = entities[id].parentID; + entities[id].root = !(parentID in entities); + } + + entityClipboard.entities = []; + + var ids = Object.keys(entities); + while (ids.length > 0) { + // Go through all remaining entities. + // If an entity does not have a parent left, move it into the list + for (var i = 0; i < ids.length; ++i) { + var id = ids[i]; + var parentID = entities[id].parentID; + if (parentID in entities) { + continue; + } + entityClipboard.entities.push(entities[id]); + delete entities[id]; + } + ids = Object.keys(entities); + } + + // Calculate size + if (entityClipboard.entities.length === 0) { + entityClipboard.dimensions = { x: 0, y: 0, z: 0 }; + entityClipboard.position = { x: 0, y: 0, z: 0 }; + } else { + var properties = entityClipboard.entities; + var brn = properties[0].boundingBox.brn; + var tfl = properties[0].boundingBox.tfl; + for (var i = 1; i < properties.length; i++) { + var bb = properties[i].boundingBox; + brn.x = Math.min(bb.brn.x, brn.x); + brn.y = Math.min(bb.brn.y, brn.y); + brn.z = Math.min(bb.brn.z, brn.z); + tfl.x = Math.max(bb.tfl.x, tfl.x); + tfl.y = Math.max(bb.tfl.y, tfl.y); + tfl.z = Math.max(bb.tfl.z, tfl.z); + } + entityClipboard.dimensions = { + x: tfl.x - brn.x, + y: tfl.y - brn.y, + z: tfl.z - brn.z + }; + entityClipboard.position = { + x: brn.x + entityClipboard.dimensions.x / 2, + y: brn.y + entityClipboard.dimensions.y / 2, + z: brn.z + entityClipboard.dimensions.z / 2 + }; + } +} + +function deepCopy(v) { + return JSON.parse(JSON.stringify(v)); +} + +function pasteEntities() { + var dims = entityClipboard.dimensions; + var maxDim = Math.max(dims.x, dims.y, dims.z); + var pastePosition = getPositionToCreateEntity(maxDim); + var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); + + var copiedProperties = [] + var ids = []; + entityClipboard.entities.forEach(function(origproperties) { + var properties = deepCopy(origproperties); + if (properties.root) { + properties.position = Vec3.sum(properties.position, deltaPosition); + delete properties.localPosition; + } else { + delete properties.position; + } + //entityProperties[properties.id] = properties; + copiedProperties.push(properties); + }); + + var currentSelections = deepCopy(SelectionManager.selections); + + function redo(copiedProperties) { + var created = createEntities(copiedProperties); + var ids = []; + for (var i = 0; i < created.length; ++i) { + ids.push(created[i].entityID); + } + SelectionManager.setSelections(ids); + } + + function undo(copiedProperties) { + for (var i = 0; i < copiedProperties.length; ++i) { + Entities.deleteEntity(copiedProperties[i].id); + } + SelectionManager.setSelections(currentSelections); + } + + redo(copiedProperties); + undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); +} + var PopupMenu = function () { var self = this; @@ -2567,6 +2766,30 @@ var PopupMenu = function () { return this; }; +var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); +mapping.from([Controller.Hardware.Keyboard.Delete]).when([!Controller.Hardware.Application.PlatformMac]).to(deleteKey); +mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware.Application.PlatformMac]).to(deleteKey); +mapping.from([Controller.Hardware.Keyboard.D]).when([Controller.Hardware.Keyboard.Control]).to(deselectKey); +mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); +mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); +mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); +mapping.from([Controller.Hardware.Keyboard.C]).when([Controller.Hardware.Keyboard.Control]).to(copyKey); +mapping.from([Controller.Hardware.Keyboard.V]).when([Controller.Hardware.Keyboard.Control]).to(pasteKey); + +function copyKey(value) { + console.log("Copy", value); + if (value > 0) { + copySelectedEntities(); + } +} + +function pasteKey(value) { + if (value > 0) { + pasteEntities(); + } +} + + var propertyMenu = new PopupMenu(); From 65fdd16d90798ab1a0efd26e3c423ef04ed5c6ba Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 19 Oct 2018 08:42:44 -0700 Subject: [PATCH 176/276] Cleanup copy/paste changes --- scripts/system/edit.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index f172df7bae..24315857c4 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2401,9 +2401,11 @@ function addChildrenEntities(parentEntityID, entityList) { } } -// Return true if the given entity with `properties` is being grabbed by an avatar. +// Determine if an entity is being grabbed. // This is mostly a heuristic - there is no perfect way to know if an entity is being // grabbed. +// +// @return {boolean} true if the given entity with `properties` is being grabbed by an avatar function nonDynamicEntityIsBeingGrabbedByAvatar(properties) { if (properties.dynamic || Uuid.isNull(properties.parentID)) { return false; @@ -2429,7 +2431,8 @@ function nonDynamicEntityIsBeingGrabbedByAvatar(properties) { } -// entityProperites - array of entity property objects +// Create the entities in entityProperties, maintaining parent-child relationships. +// @param entityPropertites {array} - Array of entity property objects function createEntities(entityProperties) { var entitiesToCreate = []; var createdEntityIDs = []; @@ -2438,7 +2441,7 @@ function createEntities(entityProperties) { SelectionManager.saveProperties(); - for (var i = 0, len = entityProperties.length; i < len; ++i) { + for (var i = 0; i < entityProperties.length; ++i) { var properties = entityProperties[i]; if (properties.parentID in originalEntityToNewEntityID) { properties.parentID = originalEntityToNewEntityID[properties.parentID]; @@ -2556,22 +2559,21 @@ function deepCopy(v) { } function pasteEntities() { - var dims = entityClipboard.dimensions; - var maxDim = Math.max(dims.x, dims.y, dims.z); - var pastePosition = getPositionToCreateEntity(maxDim); + var dimensions = entityClipboard.dimensions; + var maxDimension = Math.max(dimensions.x, dimensions.y, dimensions.z); + var pastePosition = getPositionToCreateEntity(maxDimension); var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); var copiedProperties = [] var ids = []; - entityClipboard.entities.forEach(function(origproperties) { - var properties = deepCopy(origproperties); + entityClipboard.entities.forEach(function(originalProperties) { + var properties = deepCopy(originalProperties); if (properties.root) { properties.position = Vec3.sum(properties.position, deltaPosition); delete properties.localPosition; } else { delete properties.position; } - //entityProperties[properties.id] = properties; copiedProperties.push(properties); }); @@ -2777,7 +2779,6 @@ mapping.from([Controller.Hardware.Keyboard.C]).when([Controller.Hardware.Keyboar mapping.from([Controller.Hardware.Keyboard.V]).when([Controller.Hardware.Keyboard.Control]).to(pasteKey); function copyKey(value) { - console.log("Copy", value); if (value > 0) { copySelectedEntities(); } From e0851c480775eae2195631be05df63ad59a44e9d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 19 Oct 2018 09:51:53 -0700 Subject: [PATCH 177/276] Add cut to Create, and update shortcuts --- scripts/system/edit.js | 48 +++++++++++++------- scripts/system/libraries/entityCameraTool.js | 18 ++++---- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 24315857c4..e2c4d78d96 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1254,7 +1254,7 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Redo", - shortcutKey: 'Ctrl+Shift+Z', + shortcutKey: 'Ctrl+Y', position: 1, }); @@ -2468,6 +2468,11 @@ function createEntities(entityProperties) { return createdEntityIDs; } +function cutSelectedEntities() { + copySelectedEntities(); + deleteSelectedEntities(); +} + function copySelectedEntities() { copyEntities(selectionManager.selections); } @@ -2768,28 +2773,39 @@ var PopupMenu = function () { return this; }; +function whenPressed(fn) { + return function(value) { + if (value > 0) { + fn(); + } + }; +} + +function whenReleased(fn) { + return function(value) { + if (value === 0) { + fn(); + } + }; +} + var mapping = Controller.newMapping(CONTROLLER_MAPPING_NAME); mapping.from([Controller.Hardware.Keyboard.Delete]).when([!Controller.Hardware.Application.PlatformMac]).to(deleteKey); mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware.Application.PlatformMac]).to(deleteKey); -mapping.from([Controller.Hardware.Keyboard.D]).when([Controller.Hardware.Keyboard.Control]).to(deselectKey); mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); -mapping.from([Controller.Hardware.Keyboard.C]).when([Controller.Hardware.Keyboard.Control]).to(copyKey); -mapping.from([Controller.Hardware.Keyboard.V]).when([Controller.Hardware.Keyboard.Control]).to(pasteKey); - -function copyKey(value) { - if (value > 0) { - copySelectedEntities(); - } -} - -function pasteKey(value) { - if (value > 0) { - pasteEntities(); - } -} +mapping.from([Controller.Hardware.Keyboard.X]).when([Controller.Hardware.Keyboard.Control]).to(whenReleased(cutSelectedEntities)); +mapping.from([Controller.Hardware.Keyboard.C]).when([Controller.Hardware.Keyboard.Control]).to(whenReleased(copySelectedEntities)); +mapping.from([Controller.Hardware.Keyboard.V]).when([Controller.Hardware.Keyboard.Control]).to(whenReleased(pasteEntities)); +mapping.from([Controller.Hardware.Keyboard.D]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { SelectionManager.duplicateSelection() })); +// Bind undo to ctrl-shift-z to maintain backwards-compatibility +mapping.from([Controller.Hardware.Keyboard.Z]) + .when([Controller.Hardware.Keyboard.Control, Controller.Hardware.Keyboard.Shift]) + .to(whenPressed(function() { undoHistory.redo() })); var propertyMenu = new PopupMenu(); diff --git a/scripts/system/libraries/entityCameraTool.js b/scripts/system/libraries/entityCameraTool.js index 73e73d67a6..4410f19a5e 100644 --- a/scripts/system/libraries/entityCameraTool.js +++ b/scripts/system/libraries/entityCameraTool.js @@ -98,16 +98,18 @@ CameraManager = function() { } function getActionForKeyEvent(event) { - var action = keyToActionMapping[event.key]; - if (action !== undefined) { - if (event.isShifted) { - if (action === "orbitForward") { - action = "orbitUp"; - } else if (action === "orbitBackward") { - action = "orbitDown"; + if (!event.isControl) { + var action = keyToActionMapping[event.key]; + if (action !== undefined) { + if (event.isShifted) { + if (action === "orbitForward") { + action = "orbitUp"; + } else if (action === "orbitBackward") { + action = "orbitDown"; + } } + return action; } - return action; } return null; } From 573df2d922ab975109a411fbe7db652fdc5b1e92 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 22 Oct 2018 08:41:52 -0700 Subject: [PATCH 178/276] Move copy/paste/cut to SelectionManager --- scripts/system/edit.js | 227 +----------------- .../system/libraries/entitySelectionTool.js | 174 +++++++++++++- 2 files changed, 183 insertions(+), 218 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index e2c4d78d96..b911541f79 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2390,219 +2390,6 @@ var PropertiesTool = function (opts) { return that; }; -function addChildrenEntities(parentEntityID, entityList) { - var children = Entities.getChildrenIDs(parentEntityID); - for (var i = 0; i < children.length; i++) { - var childID = children[i]; - if (entityList.indexOf(childID) < 0) { - entityList.push(childID); - } - addChildrenEntities(childID, entityList); - } -} - -// Determine if an entity is being grabbed. -// This is mostly a heuristic - there is no perfect way to know if an entity is being -// grabbed. -// -// @return {boolean} true if the given entity with `properties` is being grabbed by an avatar -function nonDynamicEntityIsBeingGrabbedByAvatar(properties) { - if (properties.dynamic || Uuid.isNull(properties.parentID)) { - return false; - } - - var avatar = AvatarList.getAvatar(properties.parentID); - if (Uuid.isNull(avatar.sessionUUID)) { - return false; - } - - var grabJointNames = [ - 'RightHand', 'LeftHand', - '_CONTROLLER_RIGHTHAND', '_CONTROLLER_LEFTHAND', - '_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND', '_CAMERA_RELATIVE_CONTROLLER_LEFTHAND']; - - for (var i = 0; i < grabJointNames.length; ++i) { - if (avatar.getJointIndex(grabJointNames[i]) === properties.parentJointIndex) { - return true; - } - } - - return false; -} - - -// Create the entities in entityProperties, maintaining parent-child relationships. -// @param entityPropertites {array} - Array of entity property objects -function createEntities(entityProperties) { - var entitiesToCreate = []; - var createdEntityIDs = []; - var createdChildrenWithOldParents = []; - var originalEntityToNewEntityID = []; - - SelectionManager.saveProperties(); - - for (var i = 0; i < entityProperties.length; ++i) { - var properties = entityProperties[i]; - if (properties.parentID in originalEntityToNewEntityID) { - properties.parentID = originalEntityToNewEntityID[properties.parentID]; - } else { - delete properties.parentID; - } - - delete properties.actionData; - var newEntityID = Entities.addEntity(properties); - - if (newEntityID) { - createdEntityIDs.push({ - entityID: newEntityID, - properties: properties - }); - if (properties.parentID !== Uuid.NULL) { - createdChildrenWithOldParents[newEntityID] = properties.parentID; - } - originalEntityToNewEntityID[properties.id] = newEntityID; - properties.id = newEntityID; - } - } - - return createdEntityIDs; -} - -function cutSelectedEntities() { - copySelectedEntities(); - deleteSelectedEntities(); -} - -function copySelectedEntities() { - copyEntities(selectionManager.selections); -} - -var entityClipboard = { - entities: {}, // Map of id -> properties for copied entities - position: { x: 0, y: 0, z: 0 }, - dimensions: { x: 0, y: 0, z: 0 }, -}; - -function copyEntities(entityIDs) { - var entityProperties = Entities.getMultipleEntityProperties(entityIDs); - var entities = {}; - entityProperties.forEach(function(props) { - entities[props.id] = props; - }); - - function appendChildren(entityID, entities) { - var childrenIDs = Entities.getChildrenIDs(entityID); - for (var i = 0; i < childrenIDs.length; ++i) { - var id = childrenIDs[i]; - if (!(id in entities)) { - entities[id] = Entities.getEntityProperties(id); - appendChildren(id, entities); - } - } - } - - var len = entityProperties.length; - for (var i = 0; i < len; ++i) { - appendChildren(entityProperties[i].id, entities); - } - - for (var id in entities) { - var parentID = entities[id].parentID; - entities[id].root = !(parentID in entities); - } - - entityClipboard.entities = []; - - var ids = Object.keys(entities); - while (ids.length > 0) { - // Go through all remaining entities. - // If an entity does not have a parent left, move it into the list - for (var i = 0; i < ids.length; ++i) { - var id = ids[i]; - var parentID = entities[id].parentID; - if (parentID in entities) { - continue; - } - entityClipboard.entities.push(entities[id]); - delete entities[id]; - } - ids = Object.keys(entities); - } - - // Calculate size - if (entityClipboard.entities.length === 0) { - entityClipboard.dimensions = { x: 0, y: 0, z: 0 }; - entityClipboard.position = { x: 0, y: 0, z: 0 }; - } else { - var properties = entityClipboard.entities; - var brn = properties[0].boundingBox.brn; - var tfl = properties[0].boundingBox.tfl; - for (var i = 1; i < properties.length; i++) { - var bb = properties[i].boundingBox; - brn.x = Math.min(bb.brn.x, brn.x); - brn.y = Math.min(bb.brn.y, brn.y); - brn.z = Math.min(bb.brn.z, brn.z); - tfl.x = Math.max(bb.tfl.x, tfl.x); - tfl.y = Math.max(bb.tfl.y, tfl.y); - tfl.z = Math.max(bb.tfl.z, tfl.z); - } - entityClipboard.dimensions = { - x: tfl.x - brn.x, - y: tfl.y - brn.y, - z: tfl.z - brn.z - }; - entityClipboard.position = { - x: brn.x + entityClipboard.dimensions.x / 2, - y: brn.y + entityClipboard.dimensions.y / 2, - z: brn.z + entityClipboard.dimensions.z / 2 - }; - } -} - -function deepCopy(v) { - return JSON.parse(JSON.stringify(v)); -} - -function pasteEntities() { - var dimensions = entityClipboard.dimensions; - var maxDimension = Math.max(dimensions.x, dimensions.y, dimensions.z); - var pastePosition = getPositionToCreateEntity(maxDimension); - var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); - - var copiedProperties = [] - var ids = []; - entityClipboard.entities.forEach(function(originalProperties) { - var properties = deepCopy(originalProperties); - if (properties.root) { - properties.position = Vec3.sum(properties.position, deltaPosition); - delete properties.localPosition; - } else { - delete properties.position; - } - copiedProperties.push(properties); - }); - - var currentSelections = deepCopy(SelectionManager.selections); - - function redo(copiedProperties) { - var created = createEntities(copiedProperties); - var ids = []; - for (var i = 0; i < created.length; ++i) { - ids.push(created[i].entityID); - } - SelectionManager.setSelections(ids); - } - - function undo(copiedProperties) { - for (var i = 0; i < copiedProperties.length; ++i) { - Entities.deleteEntity(copiedProperties[i].id); - } - SelectionManager.setSelections(currentSelections); - } - - redo(copiedProperties); - undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); -} var PopupMenu = function () { var self = this; @@ -2795,12 +2582,18 @@ mapping.from([Controller.Hardware.Keyboard.Backspace]).when([Controller.Hardware mapping.from([Controller.Hardware.Keyboard.T]).to(toggleKey); mapping.from([Controller.Hardware.Keyboard.F]).to(focusKey); mapping.from([Controller.Hardware.Keyboard.G]).to(gridKey); -mapping.from([Controller.Hardware.Keyboard.X]).when([Controller.Hardware.Keyboard.Control]).to(whenReleased(cutSelectedEntities)); -mapping.from([Controller.Hardware.Keyboard.C]).when([Controller.Hardware.Keyboard.Control]).to(whenReleased(copySelectedEntities)); -mapping.from([Controller.Hardware.Keyboard.V]).when([Controller.Hardware.Keyboard.Control]).to(whenReleased(pasteEntities)); +mapping.from([Controller.Hardware.Keyboard.X]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.cutSelectedEntities() })); +mapping.from([Controller.Hardware.Keyboard.C]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.copySelectedEntities() })); +mapping.from([Controller.Hardware.Keyboard.V]) + .when([Controller.Hardware.Keyboard.Control]) + .to(whenReleased(function() { selectionManager.pasteEntities() })); mapping.from([Controller.Hardware.Keyboard.D]) .when([Controller.Hardware.Keyboard.Control]) - .to(whenReleased(function() { SelectionManager.duplicateSelection() })); + .to(whenReleased(function() { selectionManager.duplicateSelection() })); // Bind undo to ctrl-shift-z to maintain backwards-compatibility mapping.from([Controller.Hardware.Keyboard.Z]) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 5f5225418f..843d3e986f 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -26,6 +26,11 @@ Script.include([ "./utils.js" ]); + +function deepCopy(v) { + return JSON.parse(JSON.stringify(v)); +} + SelectionManager = (function() { var that = {}; @@ -199,9 +204,11 @@ SelectionManager = (function() { } }; - // Return true if the given entity with `properties` is being grabbed by an avatar. + // Determine if an entity is being grabbed. // This is mostly a heuristic - there is no perfect way to know if an entity is being // grabbed. + // + // @return {boolean} true if the given entity with `properties` is being grabbed by an avatar function nonDynamicEntityIsBeingGrabbedByAvatar(properties) { if (properties.dynamic || Uuid.isNull(properties.parentID)) { return false; @@ -228,6 +235,12 @@ SelectionManager = (function() { return false; } + var entityClipboard = { + entities: {}, // Map of id -> properties for copied entities + position: { x: 0, y: 0, z: 0 }, + dimensions: { x: 0, y: 0, z: 0 }, + }; + that.duplicateSelection = function() { var entitiesToDuplicate = []; var duplicatedEntityIDs = []; @@ -305,6 +318,165 @@ SelectionManager = (function() { return duplicatedEntityIDs; }; + // Create the entities in entityProperties, maintaining parent-child relationships. + // @param entityPropertites {array} - Array of entity property objects + that.createEntities = function(entityProperties) { + var entitiesToCreate = []; + var createdEntityIDs = []; + var createdChildrenWithOldParents = []; + var originalEntityToNewEntityID = []; + + that.saveProperties(); + + for (var i = 0; i < entityProperties.length; ++i) { + var properties = entityProperties[i]; + if (properties.parentID in originalEntityToNewEntityID) { + properties.parentID = originalEntityToNewEntityID[properties.parentID]; + } else { + delete properties.parentID; + } + + delete properties.actionData; + var newEntityID = Entities.addEntity(properties); + + if (newEntityID) { + createdEntityIDs.push({ + entityID: newEntityID, + properties: properties + }); + if (properties.parentID !== Uuid.NULL) { + createdChildrenWithOldParents[newEntityID] = properties.parentID; + } + originalEntityToNewEntityID[properties.id] = newEntityID; + properties.id = newEntityID; + } + } + + return createdEntityIDs; + } + + that.cutSelectedEntities = function() { + copySelectedEntities(); + deleteSelectedEntities(); + } + + that.copySelectedEntities = function() { + var entityProperties = Entities.getMultipleEntityProperties(that.selections); + var entities = {}; + entityProperties.forEach(function(props) { + entities[props.id] = props; + }); + + function appendChildren(entityID, entities) { + var childrenIDs = Entities.getChildrenIDs(entityID); + for (var i = 0; i < childrenIDs.length; ++i) { + var id = childrenIDs[i]; + if (!(id in entities)) { + entities[id] = Entities.getEntityProperties(id); + appendChildren(id, entities); + } + } + } + + var len = entityProperties.length; + for (var i = 0; i < len; ++i) { + appendChildren(entityProperties[i].id, entities); + } + + for (var id in entities) { + var parentID = entities[id].parentID; + entities[id].root = !(parentID in entities); + } + + entityClipboard.entities = []; + + var ids = Object.keys(entities); + while (ids.length > 0) { + // Go through all remaining entities. + // If an entity does not have a parent left, move it into the list + for (var i = 0; i < ids.length; ++i) { + var id = ids[i]; + var parentID = entities[id].parentID; + if (parentID in entities) { + continue; + } + entityClipboard.entities.push(entities[id]); + delete entities[id]; + } + ids = Object.keys(entities); + } + + // Calculate size + if (entityClipboard.entities.length === 0) { + entityClipboard.dimensions = { x: 0, y: 0, z: 0 }; + entityClipboard.position = { x: 0, y: 0, z: 0 }; + } else { + var properties = entityClipboard.entities; + var brn = properties[0].boundingBox.brn; + var tfl = properties[0].boundingBox.tfl; + for (var i = 1; i < properties.length; i++) { + var bb = properties[i].boundingBox; + brn.x = Math.min(bb.brn.x, brn.x); + brn.y = Math.min(bb.brn.y, brn.y); + brn.z = Math.min(bb.brn.z, brn.z); + tfl.x = Math.max(bb.tfl.x, tfl.x); + tfl.y = Math.max(bb.tfl.y, tfl.y); + tfl.z = Math.max(bb.tfl.z, tfl.z); + } + entityClipboard.dimensions = { + x: tfl.x - brn.x, + y: tfl.y - brn.y, + z: tfl.z - brn.z + }; + entityClipboard.position = { + x: brn.x + entityClipboard.dimensions.x / 2, + y: brn.y + entityClipboard.dimensions.y / 2, + z: brn.z + entityClipboard.dimensions.z / 2 + }; + } + } + + that.pasteEntities = function() { + var dimensions = entityClipboard.dimensions; + var maxDimension = Math.max(dimensions.x, dimensions.y, dimensions.z); + var pastePosition = getPositionToCreateEntity(maxDimension); + var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); + + var copiedProperties = [] + var ids = []; + entityClipboard.entities.forEach(function(originalProperties) { + var properties = deepCopy(originalProperties); + if (properties.root) { + properties.position = Vec3.sum(properties.position, deltaPosition); + delete properties.localPosition; + } else { + delete properties.position; + } + copiedProperties.push(properties); + }); + + var currentSelections = deepCopy(SelectionManager.selections); + + function redo(copiedProperties) { + var created = that.createEntities(copiedProperties); + var ids = []; + for (var i = 0; i < created.length; ++i) { + ids.push(created[i].entityID); + } + SelectionManager.setSelections(ids); + } + + function undo(copiedProperties) { + for (var i = 0; i < copiedProperties.length; ++i) { + Entities.deleteEntity(copiedProperties[i].id); + } + SelectionManager.setSelections(currentSelections); + } + + redo(copiedProperties); + undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); + } + that._update = function(selectionUpdated) { var properties = null; if (that.selections.length === 0) { From 3aac294a92d9b3331fe2765032266f2844902e07 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Sep 2018 16:57:22 -0700 Subject: [PATCH 179/276] optimize sockAddrBelongsToNode and reduce connection filter usage --- libraries/networking/src/LimitedNodeList.cpp | 12 +++++++++++- libraries/networking/src/LimitedNodeList.h | 2 +- libraries/networking/src/udt/Socket.cpp | 10 +++++----- libraries/networking/src/udt/Socket.h | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index db6ed15792..7cae5d8a71 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -1187,12 +1187,22 @@ void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSock SharedNodePointer LimitedNodeList::findNodeWithAddr(const HifiSockAddr& addr) { QReadLocker locker(&_nodeMutex); - auto it = std::find_if(std::begin(_nodeHash), std::end(_nodeHash), [&](const UUIDNodePair& pair) { + auto it = std::find_if(std::begin(_nodeHash), std::end(_nodeHash), [&addr](const UUIDNodePair& pair) { return pair.second->getActiveSocket() ? (*pair.second->getActiveSocket() == addr) : false; }); return (it != std::end(_nodeHash)) ? it->second : SharedNodePointer(); } +bool LimitedNodeList::sockAddrBelongsToNode(const HifiSockAddr& sockAddr) { + QReadLocker locker(&_nodeMutex); + auto it = std::find_if(std::begin(_nodeHash), std::end(_nodeHash), [&sockAddr](const UUIDNodePair& pair) { + return pair.second->getPublicSocket() == sockAddr + || pair.second->getLocalSocket() == sockAddr + || pair.second->getSymmetricSocket() == sockAddr; + }); + return it != std::end(_nodeHash); +} + void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID) { auto icePacket = NLPacket::create(packetType); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index cffc49521a..dacefa8e40 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -386,7 +386,7 @@ protected: void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerRequestID = QUuid()); - bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr) { return findNodeWithAddr(sockAddr) != SharedNodePointer(); } + bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr); NodeHash _nodeHash; mutable QReadWriteLock _nodeMutex { QReadWriteLock::Recursive }; diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 6de43219e5..25e6fae023 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -228,13 +228,13 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& soc return bytesWritten; } -Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) { +Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr, bool filterCreate) { auto it = _connectionsHash.find(sockAddr); if (it == _connectionsHash.end()) { // we did not have a matching connection, time to see if we should make one - if (_connectionCreationFilterOperator && !_connectionCreationFilterOperator(sockAddr)) { + if (filterCreate && _connectionCreationFilterOperator && !_connectionCreationFilterOperator(sockAddr)) { // the connection creation filter did not allow us to create a new connection #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Socket::findOrCreateConnection refusing to create connection for" << sockAddr @@ -376,7 +376,7 @@ void Socket::readPendingDatagrams() { controlPacket->setReceiveTime(receiveTime); // move this control packet to the matching connection, if there is one - auto connection = findOrCreateConnection(senderSockAddr); + auto connection = findOrCreateConnection(senderSockAddr, true); if (connection) { connection->processControl(move(controlPacket)); @@ -394,7 +394,7 @@ void Socket::readPendingDatagrams() { if (!_packetFilterOperator || _packetFilterOperator(*packet)) { if (packet->isReliable()) { // if this was a reliable packet then signal the matching connection with the sequence number - auto connection = findOrCreateConnection(senderSockAddr); + auto connection = findOrCreateConnection(senderSockAddr, true); if (!connection || !connection->processReceivedSequenceNumber(packet->getSequenceNumber(), packet->getDataSize(), @@ -409,7 +409,7 @@ void Socket::readPendingDatagrams() { } if (packet->isPartOfMessage()) { - auto connection = findOrCreateConnection(senderSockAddr); + auto connection = findOrCreateConnection(senderSockAddr, true); if (connection) { connection->queueReceivedMessagePacket(std::move(packet)); } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 30058e1d23..99266e105e 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -109,7 +109,7 @@ private slots: private: void setSystemBufferSizes(); - Connection* findOrCreateConnection(const HifiSockAddr& sockAddr); + Connection* findOrCreateConnection(const HifiSockAddr& sockAddr, bool filterCreation = false); bool socketMatchesNodeOrDomain(const HifiSockAddr& sockAddr); // privatized methods used by UDTTest - they are private since they must be called on the Socket thread From 493262052cde39fd38090960d9332d05f6602504 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 11:32:31 -0700 Subject: [PATCH 180/276] Remove extraneous code changes; remove QUrlAncestry code; remove extra logging --- assignment-client/src/Agent.cpp | 2 +- assignment-client/src/AssignmentClient.cpp | 11 +- interface/src/Application.cpp | 7 +- interface/src/assets/ATPAssetMigrator.cpp | 14 +- .../scripting/ClipboardScriptingInterface.h | 2 +- interface/src/ui/overlays/Web3DOverlay.cpp | 5 +- libraries/avatars/src/AvatarData.cpp | 18 +- libraries/entities/src/EntityEditFilters.cpp | 34 ++-- libraries/entities/src/EntityTree.cpp | 16 +- libraries/entities/src/EntityTree.h | 10 +- libraries/fbx/src/GLTFReader.cpp | 190 +++++++++--------- libraries/fbx/src/OBJReader.cpp | 26 +-- .../networking/src/AssetResourceRequest.cpp | 4 +- .../networking/src/FileResourceRequest.h | 8 +- .../networking/src/HTTPResourceRequest.h | 7 +- .../networking/src/NetworkAccessManager.cpp | 2 +- libraries/networking/src/ResourceCache.cpp | 19 +- libraries/networking/src/ResourceManager.cpp | 3 - libraries/networking/src/ResourceRequest.cpp | 3 +- libraries/networking/src/ResourceRequest.h | 11 +- libraries/octree/src/Octree.cpp | 21 +- libraries/octree/src/Octree.h | 11 +- libraries/qml/src/qml/OffscreenSurface.cpp | 5 - .../src/FileScriptingInterface.cpp | 4 +- .../script-engine/src/XMLHttpRequestClass.cpp | 4 +- .../EntityItemWeakPointerWithUrlAncestry.h | 31 --- libraries/shared/src/QUrlAncestry.cpp | 35 ---- libraries/shared/src/QUrlAncestry.h | 32 --- .../shared/src/ResourceRequestObserver.cpp | 16 +- .../shared/src/ResourceRequestObserver.h | 4 +- 30 files changed, 210 insertions(+), 345 deletions(-) delete mode 100644 libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h delete mode 100644 libraries/shared/src/QUrlAncestry.cpp delete mode 100644 libraries/shared/src/QUrlAncestry.h diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 1561af4d25..4490474599 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -895,7 +895,7 @@ void Agent::aboutToFinish() { { DependencyManager::get()->shutdownScripting(); } - + DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 06b3f4da86..76ff5ab2ed 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -37,7 +37,6 @@ #include "AssignmentFactory.h" #include "ResourceRequestObserver.h" - const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; @@ -162,7 +161,7 @@ void AssignmentClient::setUpStatusToMonitor() { void AssignmentClient::sendStatusPacketToACM() { // tell the assignment client monitor what this assignment client is doing (if anything) auto nodeList = DependencyManager::get(); - + quint8 assignmentType = Assignment::Type::AllTypes; if (_currentAssignment) { @@ -173,7 +172,7 @@ void AssignmentClient::sendStatusPacketToACM() { statusPacket->write(_childAssignmentUUID.toRfc4122()); statusPacket->writePrimitive(assignmentType); - + nodeList->sendPacket(std::move(statusPacket), _assignmentClientMonitorSocket); } @@ -259,10 +258,10 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer message) { const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); - + if (senderSockAddr.getAddress() == QHostAddress::LocalHost || senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) { - + qCDebug(assignment_client) << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode."; QCoreApplication::quit(); } else { @@ -315,6 +314,6 @@ void AssignmentClient::assignmentCompleted() { nodeList->setOwnerType(NodeType::Unassigned); nodeList->reset(); nodeList->resetNodeInterestSet(); - + _isAssigned = false; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 242445b0fe..e515a22403 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -170,7 +170,6 @@ #include "ModelPackager.h" #include "scripting/Audio.h" #include "networking/CloseEventSender.h" -#include "QUrlAncestry.h" #include "scripting/TestScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/ClipboardScriptingInterface.h" @@ -947,6 +946,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); @@ -1783,7 +1783,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo updateHeartbeat(); QTimer* settingsTimer = new QTimer(); moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{ - // This needs to run on the settings thread, so we need to pass the `settingsTimer` as the + // This needs to run on the settings thread, so we need to pass the `settingsTimer` as the // receiver object, otherwise it will run on the application thread and trigger a warning // about trying to kill the timer on the main thread. connect(qApp, &Application::beforeAboutToQuit, settingsTimer, [this, settingsTimer]{ @@ -5029,7 +5029,8 @@ bool Application::importEntities(const QString& urlOrFilename, const bool isObse bool success = false; _entityClipboard->withWriteLock([&] { _entityClipboard->eraseAllOctreeElements(); - success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId, QUrlAncestry()); + + success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId); if (success) { _entityClipboard->reaverageOctreeElements(); } diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp index 6912c69db8..be7f2014cc 100644 --- a/interface/src/assets/ATPAssetMigrator.cpp +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -203,7 +203,7 @@ void ATPAssetMigrator::loadEntityServerFile() { void ATPAssetMigrator::migrateResource(ResourceRequest* request) { // use an asset client to upload the asset auto assetClient = DependencyManager::get(); - + auto upload = assetClient->createUpload(request->getData()); // add this URL to our hash of AssetUpload to original URL @@ -243,7 +243,7 @@ void ATPAssetMigrator::assetUploadFinished(AssetUpload *upload, const QString& h } checkIfFinished(); - + upload->deleteLater(); } @@ -299,24 +299,24 @@ void ATPAssetMigrator::checkIfFinished() { bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) { static bool hasAskedForCompleteMigration { false }; static bool wantsCompleteMigration { false }; - + if (!hasAskedForCompleteMigration) { // this is the first resource migration - ask the user if they just want to migrate everything static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n"\ "Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n"\ "Select \"No\" to be prompted for each discovered asset." }; - + auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - + if (button == QMessageBox::Yes) { wantsCompleteMigration = true; } - + hasAskedForCompleteMigration = true; } - + if (wantsCompleteMigration) { return true; } else { diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 535ccfd5ab..60b6ca2e03 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -64,7 +64,7 @@ public: * @returns {boolean} true if the export was successful, otherwise false. */ Q_INVOKABLE bool exportEntities(const QString& filename, const QVector& entityIDs); - + /**jsdoc * Export the entities with centers within a cube to a JSON file. * @function Clipboard.exportEntities diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 53505c2013..084615cae2 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -61,7 +61,6 @@ #include "AboutUtil.h" #include "ResourceRequestObserver.h" - static int MAX_WINDOW_SIZE = 4096; static const float METERS_TO_INCHES = 39.3701f; static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; @@ -539,7 +538,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. * * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. @@ -564,7 +563,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {string} url - The URL of the Web page to display. * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. * @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay. - * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: + * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: * scale, size. * @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second. * @property {boolean} showKeyboardFocusHighlight=true - If true, the Web overlay is highlighted when it has diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 753707f0fe..032ffe25f7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -380,7 +380,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } else { AVATAR_MEMCPY(_globalPosition); } - + int numBytes = destinationBuffer - startSection; @@ -648,7 +648,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (!data.translationIsDefaultPose) { 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++; @@ -1055,7 +1055,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); - + bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected); @@ -1527,7 +1527,7 @@ glm::vec3 AvatarData::getJointTranslation(int index) const { } glm::vec3 AvatarData::getJointTranslation(const QString& name) const { - // Can't do this, because the lock needs to cover the entire set of logic. In theory, the joints could change + // Can't do this, because the lock needs to cover the entire set of logic. In theory, the joints could change // on another thread in between the call to getJointIndex and getJointTranslation // return getJointTranslation(getJointIndex(name)); return readLockWithNamedJointIndex(name, [this](int index) { @@ -1608,7 +1608,7 @@ bool AvatarData::isJointDataValid(const QString& name) const { // return isJointDataValid(getJointIndex(name)); return readLockWithNamedJointIndex(name, false, [&](int index) { - // This is technically superfluous.... the lambda is only called if index is a valid + // This is technically superfluous.... the lambda is only called if index is a valid // offset for _jointData. Nevertheless, it would be confusing to leave the lamdba as // `return true` return index < _jointData.size(); @@ -1827,7 +1827,7 @@ qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { bytesWritten += destination.writePrimitive(traitVersion); } - + AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size(); bytesWritten += destination.writePrimitive(encodedURLSize); @@ -1936,7 +1936,7 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { if (expanded == _skeletonModelURL) { return; } - + _skeletonModelURL = expanded; qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString(); @@ -2163,7 +2163,7 @@ void AvatarData::updateJointMappings() { if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { //// - // TODO: Should we rely upon HTTPResourceRequest instead? + // TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead? // HTTPResourceRequest::doSend() covers all of the following and // then some. It doesn't cover the connect() call, so we may // want to add a HTTPResourceRequest::doSend() method that does @@ -2402,7 +2402,7 @@ QJsonObject AvatarData::toJson() const { for (auto entityID : _avatarEntityData.keys()) { QVariantMap entityData; QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID; - entityData.insert("id", newId); + entityData.insert("id", newId); entityData.insert("properties", _avatarEntityData.value(entityID).toBase64()); avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); } diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index c88ae138bf..4865c0ba1e 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -24,7 +24,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { for (auto id : zoneIDs) { if (!id.isInvalidID()) { // for now, look it up in the tree (soon we need to cache or similar?) - EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); + EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); auto zone = std::dynamic_pointer_cast(itemPtr); if (!zone) { // TODO: maybe remove later? @@ -33,7 +33,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { zones.append(id); } } else { - // the null id is the global filter we put in the domain server's + // the null id is the global filter we put in the domain server's // advanced entity server settings zones.append(id); } @@ -43,7 +43,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType, EntityItemID& itemID, EntityItemPointer& existingEntity) { - + // get the ids of all the zones (plus the global entity edit filter) that the position // lies within auto zoneIDs = getZonesByPosition(position); @@ -51,12 +51,12 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper if (!itemID.isInvalidID() && id == itemID) { continue; } - - // get the filter pair, etc... + + // get the filter pair, etc... _lock.lockForRead(); FilterData filterData = _filterDataMap.value(id); _lock.unlock(); - + if (filterData.valid()) { if (filterData.rejectAll) { return false; @@ -153,13 +153,13 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper // otherwise, assume it wants to pass all properties propertiesOut = propertiesIn; wasChanged = false; - + } else { return false; } } } - // if we made it here, + // if we made it here, return true; } @@ -175,23 +175,23 @@ void EntityEditFilters::removeFilter(EntityItemID entityID) { void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { QUrl scriptURL(filterURL); - - // setting it to an empty string is same as removing + + // setting it to an empty string is same as removing if (filterURL.size() == 0) { removeFilter(entityID); return; } - + // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) { qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer."; scriptRequestFinished(entityID); return; } - + // first remove any existing info for this entity removeFilter(entityID); - + // reject all edits until we load the script FilterData filterData; filterData.rejectAll = true; @@ -199,7 +199,7 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { _lock.lockForWrite(); _filterDataMap.insert(entityID, filterData); _lock.unlock(); - + auto scriptRequest = DependencyManager::get()->createResourceRequest( this, scriptURL, true, -1, "EntityEditFilters::addFilter"); if (!scriptRequest) { @@ -265,7 +265,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { FilterData filterData; filterData.engine = engine; filterData.rejectAll = false; - + // define the uncaughtException function QScriptEngine& engineRef = *engine; filterData.uncaughtExceptions = [&engineRef, urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; @@ -369,11 +369,11 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { _lock.unlock(); qDebug() << "script request filter processed for entity id " << entityID; - + emit filterAdded(entityID, true); return; } - } + } } else if (scriptRequest) { const QString urlString = scriptRequest->getUrl().toString(); qCritical() << "Failed to download script at" << urlString; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8992157681..0b3b8abba2 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -38,8 +38,6 @@ #include "LogHandler.h" #include "EntityEditFilters.h" #include "EntityDynamicFactoryInterface.h" -#include "QUrlAncestry.h" - static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour @@ -100,7 +98,7 @@ EntityTree::~EntityTree() { eraseAllOctreeElements(false); } -void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { +void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { _entityScriptSourceWhitelist = entityScriptSourceWhitelist.split(',', QString::SkipEmptyParts); } @@ -862,7 +860,7 @@ float findRayIntersectionSortingOp(const OctreeElementPointer& element, void* ex EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, + bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { @@ -1353,7 +1351,7 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity key = sent.second; } - QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----\n"; + QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----\n"; QByteArray hashedActualNonce = QCryptographicHash::hash(QByteArray(actualNonce.toUtf8()), QCryptographicHash::Sha256); bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), hashedActualNonce, QByteArray::fromBase64(nonce.toUtf8())); @@ -1797,7 +1795,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c if (newEntity) { newEntity->markAsChangedOnServer(); notifyNewlyCreatedEntity(*newEntity, senderNode); - + startLogging = usecTimestampNow(); if (wantEditLogging()) { qCDebug(entities) << "User [" << senderNode->getUUID() << "] added entity. ID:" @@ -1822,7 +1820,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c } } else { HIFI_FCDEBUG(entities(), "Edit failed. [" << message.getType() <<"] " << - "entity id:" << entityItemID << + "entity id:" << entityItemID << "existingEntity pointer:" << existingEntity.get()); } } @@ -2043,7 +2041,7 @@ bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) { if (hasSomethingNewer) { int elapsed = usecTimestampNow() - considerEntitiesSince; int difference = considerEntitiesSince - sinceTime; - qCDebug(entities) << "EntityTree::hasEntitiesDeletedSince() sinceTime:" << sinceTime + qCDebug(entities) << "EntityTree::hasEntitiesDeletedSince() sinceTime:" << sinceTime << "considerEntitiesSince:" << considerEntitiesSince << "elapsed:" << elapsed << "difference:" << difference; } #endif @@ -2495,7 +2493,7 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer return true; } -bool EntityTree::readFromMap(QVariantMap& map, const QUrlAncestry& ancestry) { +bool EntityTree::readFromMap(QVariantMap& map) { // These are needed to deal with older content (before adding inheritance modes) int contentVersion = map["Version"].toInt(); bool needsConversion = (contentVersion < (int)EntityVersion::ZoneLightInheritModes); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 8c787f8eb8..2f971b8566 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -22,8 +22,6 @@ #include "EntityTreeElement.h" #include "DeleteEntityOperator.h" #include "MovingEntitiesOperator.h" -#include "QUrlAncestry.h" - class EntityTree; using EntityTreePointer = std::shared_ptr; @@ -96,7 +94,7 @@ public: virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, + bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); @@ -172,7 +170,7 @@ public: void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); - bool hasAnyDeletedEntities() const { + bool hasAnyDeletedEntities() const { QReadLocker locker(&_recentlyDeletedEntitiesLock); return _recentlyDeletedEntityItemIDs.size() > 0; } @@ -180,7 +178,7 @@ public: bool hasEntitiesDeletedSince(quint64 sinceTime); static quint64 getAdjustedConsiderSince(quint64 sinceTime); - QMultiMap getRecentlyDeletedEntityIDs() const { + QMultiMap getRecentlyDeletedEntityIDs() const { QReadLocker locker(&_recentlyDeletedEntitiesLock); return _recentlyDeletedEntityItemIDs; } @@ -225,7 +223,7 @@ public: virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) override; - virtual bool readFromMap(QVariantMap& entityDescription, const QUrlAncestry& ancestry = {}) override; + virtual bool readFromMap(QVariantMap& entityDescription) override; glm::vec3 getContentsDimensions(); float getContentsLargestDimension(); diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp index 89592c399c..b93dc3541b 100644 --- a/libraries/fbx/src/GLTFReader.cpp +++ b/libraries/fbx/src/GLTFReader.cpp @@ -40,7 +40,7 @@ GLTFReader::GLTFReader() { } -bool GLTFReader::getStringVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getStringVal(const QJsonObject& object, const QString& fieldname, QString& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isString()); if (_defined) { @@ -60,7 +60,7 @@ bool GLTFReader::getBoolVal(const QJsonObject& object, const QString& fieldname, return _defined; } -bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, int& value, QMap& defined) { bool _defined = (object.contains(fieldname) && !object[fieldname].isNull()); if (_defined) { @@ -70,7 +70,7 @@ bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, return _defined; } -bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldname, double& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isDouble()); if (_defined) { @@ -79,7 +79,7 @@ bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldnam defined.insert(fieldname, _defined); return _defined; } -bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldname, QJsonObject& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isObject()); if (_defined) { @@ -89,7 +89,7 @@ bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldnam return _defined; } -bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldname, QVector& values, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { @@ -104,7 +104,7 @@ bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldn return _defined; } -bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, QVector& values, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { @@ -119,7 +119,7 @@ bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fie return _defined; } -bool GLTFReader::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, QJsonArray& objects, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { @@ -229,7 +229,7 @@ bool GLTFReader::setAsset(const QJsonObject& object) { QJsonObject jsAsset; bool isAssetDefined = getObjectVal(object, "asset", jsAsset, _file.defined); if (isAssetDefined) { - if (!getStringVal(jsAsset, "version", _file.asset.version, + if (!getStringVal(jsAsset, "version", _file.asset.version, _file.asset.defined) || _file.asset.version != "2.0") { return false; } @@ -241,7 +241,7 @@ bool GLTFReader::setAsset(const QJsonObject& object) { bool GLTFReader::addAccessor(const QJsonObject& object) { GLTFAccessor accessor; - + getIntVal(object, "bufferView", accessor.bufferView, accessor.defined); getIntVal(object, "byteOffset", accessor.byteOffset, accessor.defined); getIntVal(object, "componentType", accessor.componentType, accessor.defined); @@ -261,7 +261,7 @@ bool GLTFReader::addAccessor(const QJsonObject& object) { bool GLTFReader::addAnimation(const QJsonObject& object) { GLTFAnimation animation; - + QJsonArray channels; if (getObjectArrayVal(object, "channels", channels, animation.defined)) { foreach(const QJsonValue & v, channels) { @@ -272,7 +272,7 @@ bool GLTFReader::addAnimation(const QJsonObject& object) { if (getObjectVal(v.toObject(), "target", jsChannel, channel.defined)) { getIntVal(jsChannel, "node", channel.target.node, channel.target.defined); getIntVal(jsChannel, "path", channel.target.path, channel.target.defined); - } + } } } } @@ -291,7 +291,7 @@ bool GLTFReader::addAnimation(const QJsonObject& object) { } } } - + _file.animations.push_back(animation); return true; @@ -299,20 +299,20 @@ bool GLTFReader::addAnimation(const QJsonObject& object) { bool GLTFReader::addBufferView(const QJsonObject& object) { GLTFBufferView bufferview; - + getIntVal(object, "buffer", bufferview.buffer, bufferview.defined); getIntVal(object, "byteLength", bufferview.byteLength, bufferview.defined); getIntVal(object, "byteOffset", bufferview.byteOffset, bufferview.defined); getIntVal(object, "target", bufferview.target, bufferview.defined); - + _file.bufferviews.push_back(bufferview); - + return true; } bool GLTFReader::addBuffer(const QJsonObject& object) { GLTFBuffer buffer; - + getIntVal(object, "byteLength", buffer.byteLength, buffer.defined); if (getStringVal(object, "uri", buffer.uri, buffer.defined)) { if (!readBinary(buffer.uri, buffer.blob)) { @@ -320,13 +320,13 @@ bool GLTFReader::addBuffer(const QJsonObject& object) { } } _file.buffers.push_back(buffer); - + return true; } bool GLTFReader::addCamera(const QJsonObject& object) { GLTFCamera camera; - + QJsonObject jsPerspective; QJsonObject jsOrthographic; QString type; @@ -346,28 +346,28 @@ bool GLTFReader::addCamera(const QJsonObject& object) { } else if (getStringVal(object, "type", type, camera.defined)) { camera.type = getCameraType(type); } - + _file.cameras.push_back(camera); - + return true; } bool GLTFReader::addImage(const QJsonObject& object) { GLTFImage image; - + QString mime; getStringVal(object, "uri", image.uri, image.defined); if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); } getIntVal(object, "bufferView", image.bufferView, image.defined); - + _file.images.push_back(image); return true; } -bool GLTFReader::getIndexFromObject(const QJsonObject& object, const QString& field, +bool GLTFReader::getIndexFromObject(const QJsonObject& object, const QString& field, int& outidx, QMap& defined) { QJsonObject subobject; if (getObjectVal(object, field, subobject, defined)) { @@ -393,20 +393,20 @@ bool GLTFReader::addMaterial(const QJsonObject& object) { getDoubleVal(object, "alphaCutoff", material.alphaCutoff, material.defined); QJsonObject jsMetallicRoughness; if (getObjectVal(object, "pbrMetallicRoughness", jsMetallicRoughness, material.defined)) { - getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", - material.pbrMetallicRoughness.baseColorFactor, + getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", + material.pbrMetallicRoughness.baseColorFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "baseColorTexture", - material.pbrMetallicRoughness.baseColorTexture, + getIndexFromObject(jsMetallicRoughness, "baseColorTexture", + material.pbrMetallicRoughness.baseColorTexture, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "metallicFactor", - material.pbrMetallicRoughness.metallicFactor, + getDoubleVal(jsMetallicRoughness, "metallicFactor", + material.pbrMetallicRoughness.metallicFactor, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "roughnessFactor", - material.pbrMetallicRoughness.roughnessFactor, + getDoubleVal(jsMetallicRoughness, "roughnessFactor", + material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", - material.pbrMetallicRoughness.metallicRoughnessTexture, + getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", + material.pbrMetallicRoughness.metallicRoughnessTexture, material.pbrMetallicRoughness.defined); } _file.materials.push_back(material); @@ -428,7 +428,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) { getIntVal(jsPrimitive, "mode", primitive.mode, primitive.defined); getIntVal(jsPrimitive, "indices", primitive.indices, primitive.defined); getIntVal(jsPrimitive, "material", primitive.material, primitive.defined); - + QJsonObject jsAttributes; if (getObjectVal(jsPrimitive, "attributes", jsAttributes, primitive.defined)) { QStringList attrKeys = jsAttributes.keys(); @@ -455,7 +455,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) { primitive.targets.push_back(target); } } - } + } mesh.primitives.push_back(primitive); } } @@ -469,7 +469,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) { bool GLTFReader::addNode(const QJsonObject& object) { GLTFNode node; - + getStringVal(object, "name", node.name, node.defined); getIntVal(object, "camera", node.camera, node.defined); getIntVal(object, "mesh", node.mesh, node.defined); @@ -524,10 +524,10 @@ bool GLTFReader::addSkin(const QJsonObject& object) { } bool GLTFReader::addTexture(const QJsonObject& object) { - GLTFTexture texture; + GLTFTexture texture; getIntVal(object, "sampler", texture.sampler, texture.defined); getIntVal(object, "source", texture.source, texture.defined); - + _file.textures.push_back(texture); return true; @@ -535,7 +535,7 @@ bool GLTFReader::addTexture(const QJsonObject& object) { bool GLTFReader::parseGLTF(const QByteArray& model) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); - + QJsonDocument d = QJsonDocument::fromJson(model); QJsonObject jsFile = d.object(); @@ -707,25 +707,25 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { foreach(int child, node.children) nodeDependencies[child].push_back(nodecount); nodecount++; } - + nodecount = 0; foreach(auto &node, _file.nodes) { // collect node transform - _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); + _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); if (nodeDependencies[nodecount].size() == 1) { int parentidx = nodeDependencies[nodecount][0]; while (true) { // iterate parents // collect parents transforms - _file.nodes[nodecount].transforms.push_back(getModelTransform(_file.nodes[parentidx])); + _file.nodes[nodecount].transforms.push_back(getModelTransform(_file.nodes[parentidx])); if (nodeDependencies[parentidx].size() == 1) { parentidx = nodeDependencies[parentidx][0]; } else break; } } - + nodecount++; } - + //Build default joints geometry.joints.resize(1); geometry.joints[0].isFree = false; @@ -756,7 +756,7 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { setFBXMaterial(fbxMaterial, _file.materials[i]); } - + nodecount = 0; // Build meshes @@ -789,11 +789,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { QVector raw_vertices; QVector raw_normals; - bool success = addArrayOfType(indicesBuffer.blob, - indicesBufferview.byteOffset + indicesAccBoffset, - indicesAccessor.count, - part.triangleIndices, - indicesAccessor.type, + bool success = addArrayOfType(indicesBuffer.blob, + indicesBufferview.byteOffset + indicesAccBoffset, + indicesAccessor.count, + part.triangleIndices, + indicesAccessor.type, indicesAccessor.componentType); if (!success) { @@ -813,10 +813,10 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0; if (key == "POSITION") { QVector vertices; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, vertices, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, vertices, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; @@ -827,11 +827,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } else if (key == "NORMAL") { QVector normals; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, - normals, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + normals, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; @@ -842,11 +842,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } else if (key == "TEXCOORD_0") { QVector texcoords; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, - texcoords, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + texcoords, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; @@ -857,11 +857,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } else if (key == "TEXCOORD_1") { QVector texcoords; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, - texcoords, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + texcoords, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; @@ -888,8 +888,8 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { mesh.meshExtents.addPoint(vertex); geometry.meshExtents.addPoint(vertex); } - - // since mesh.modelTransform seems to not have any effect I apply the transformation the model + + // since mesh.modelTransform seems to not have any effect I apply the transformation the model for (int h = 0; h < mesh.vertices.size(); h++) { glm::vec4 ver = glm::vec4(mesh.vertices[h], 1); if (node.transforms.size() > 0) { @@ -901,18 +901,18 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { mesh.meshIndex = geometry.meshes.size(); FBXReader::buildModelMesh(mesh, url.toString()); } - + } nodecount++; } - + return true; } -FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, +FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) { - + _url = url; // Normalize url for local files @@ -928,10 +928,10 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping FBXGeometry& geometry = *geometryPtr; buildGeometry(geometry, url); - + //fbxDebugDump(geometry); return geometryPtr; - + } bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { @@ -940,7 +940,7 @@ bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url; bool success; std::tie(success, outdata) = requestData(binaryUrl); - + return success; } @@ -1000,7 +1000,7 @@ QNetworkReply* GLTFReader::request(QUrl& url, bool isTest) { FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { FBXTexture fbxtex = FBXTexture(); fbxtex.texcoordSet = 0; - + if (texture.defined["source"]) { QString url = _file.images[texture.source].uri; QString fname = QUrl(url).fileName(); @@ -1020,10 +1020,10 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia if (material.defined["name"]) { fbxmat.name = fbxmat.materialID = material.name; } - + if (material.defined["emissiveFactor"] && material.emissiveFactor.size() == 3) { - glm::vec3 emissive = glm::vec3(material.emissiveFactor[0], - material.emissiveFactor[1], + glm::vec3 emissive = glm::vec3(material.emissiveFactor[0], + material.emissiveFactor[1], material.emissiveFactor[2]); fbxmat._material->setEmissive(emissive); } @@ -1032,12 +1032,12 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia fbxmat.emissiveTexture = getFBXTexture(_file.textures[material.emissiveTexture]); fbxmat.useEmissiveMap = true; } - + if (material.defined["normalTexture"]) { fbxmat.normalTexture = getFBXTexture(_file.textures[material.normalTexture]); fbxmat.useNormalMap = true; } - + if (material.defined["occlusionTexture"]) { fbxmat.occlusionTexture = getFBXTexture(_file.textures[material.occlusionTexture]); fbxmat.useOcclusionMap = true; @@ -1045,7 +1045,7 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia if (material.defined["pbrMetallicRoughness"]) { fbxmat.isPBSMaterial = true; - + if (material.pbrMetallicRoughness.defined["metallicFactor"]) { fbxmat.metallic = material.pbrMetallicRoughness.metallicFactor; } @@ -1063,23 +1063,23 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { fbxmat._material->setRoughness(material.pbrMetallicRoughness.roughnessFactor); } - if (material.pbrMetallicRoughness.defined["baseColorFactor"] && + if (material.pbrMetallicRoughness.defined["baseColorFactor"] && material.pbrMetallicRoughness.baseColorFactor.size() == 4) { - glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], - material.pbrMetallicRoughness.baseColorFactor[1], + glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], + material.pbrMetallicRoughness.baseColorFactor[1], material.pbrMetallicRoughness.baseColorFactor[2]); fbxmat.diffuseColor = dcolor; fbxmat._material->setAlbedo(dcolor); fbxmat._material->setOpacity(material.pbrMetallicRoughness.baseColorFactor[3]); - } + } } } template -bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, +bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType) { - + QDataStream blobstream(bin); blobstream.setByteOrder(QDataStream::LittleEndian); blobstream.setVersion(QDataStream::Qt_5_9); @@ -1134,9 +1134,9 @@ bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, return true; } template -bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count, +bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType, int componentType) { - + switch (componentType) { case GLTFAccessorComponentType::BYTE: {} case GLTFAccessorComponentType::UNSIGNED_BYTE: { @@ -1158,8 +1158,8 @@ bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count return false; } -void GLTFReader::retriangulate(const QVector& inIndices, const QVector& in_vertices, - const QVector& in_normals, QVector& outIndices, +void GLTFReader::retriangulate(const QVector& inIndices, const QVector& in_vertices, + const QVector& in_normals, QVector& outIndices, QVector& out_vertices, QVector& out_normals) { for (int i = 0; i < inIndices.size(); i = i + 3) { diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 00109e9030..7a2cbff497 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -15,7 +15,7 @@ #include "OBJReader.h" #include // .obj files are not locale-specific. The C/ASCII charset applies. -#include +#include #include #include @@ -263,16 +263,16 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG - qCDebug(modelformat) << + qCDebug(modelformat) << "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << - " shininess:" << currentMaterial.shininess << + " 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 << + " 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 @@ -352,7 +352,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } } } -} +} void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions) { // Texture options reference http://paulbourke.net/dataformats/mtl/ @@ -794,7 +794,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m n0 = checked_at(normals, face.normalIndices[0]); n1 = checked_at(normals, face.normalIndices[1]); n2 = checked_at(normals, face.normalIndices[2]); - } else { + } else { // generate normals from triangle plane if not provided n0 = n1 = n2 = glm::cross(v1 - v0, v2 - v0); } @@ -924,7 +924,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m bool applyNonMetallic = false; bool fresnelOn = false; - // Illumination model reference http://paulbourke.net/dataformats/mtl/ + // Illumination model reference http://paulbourke.net/dataformats/mtl/ switch (objMaterial.illuminationModel) { case 0: // Color on and Ambient off // We don't support ambient = do nothing? @@ -968,7 +968,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m case 10: // Casts shadows onto invisible surfaces // Do nothing? break; - } + } if (applyTransparency) { fbxMaterial.opacity = std::max(fbxMaterial.opacity, ILLUMINATION_MODEL_MIN_OPACITY); diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 23ab1548a0..6f5b13f98d 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -39,7 +39,7 @@ AssetResourceRequest::~AssetResourceRequest() { if (_assetMappingRequest) { _assetMappingRequest->deleteLater(); } - + if (_assetRequest) { _assetRequest->deleteLater(); } @@ -82,7 +82,7 @@ void AssetResourceRequest::requestMappingForPath(const AssetUtils::AssetPath& pa // make sure we'll hear about the result of the get mapping request connect(_assetMappingRequest, &GetMappingRequest::finished, this, [this, path](GetMappingRequest* request){ auto statTracker = DependencyManager::get(); - + Q_ASSERT(_state == InProgress); Q_ASSERT(request == _assetMappingRequest); diff --git a/libraries/networking/src/FileResourceRequest.h b/libraries/networking/src/FileResourceRequest.h index 12b5b7d72e..fa9a1617a9 100644 --- a/libraries/networking/src/FileResourceRequest.h +++ b/libraries/networking/src/FileResourceRequest.h @@ -12,19 +12,19 @@ #ifndef hifi_FileResourceRequest_h #define hifi_FileResourceRequest_h -#include "ResourceRequest.h" -#include "QUrlAncestry.h" +#include +#include "ResourceRequest.h" class FileResourceRequest : public ResourceRequest { Q_OBJECT public: FileResourceRequest( - const QUrlAncestry& urlAncestry, + const QUrl& url, const bool isObservable = true, const qint64 callerId = -1, const QString& extra = "" - ) : ResourceRequest(urlAncestry, isObservable, callerId, extra) { } + ) : ResourceRequest(url, isObservable, callerId, extra) { } protected: virtual void doSend() override; diff --git a/libraries/networking/src/HTTPResourceRequest.h b/libraries/networking/src/HTTPResourceRequest.h index 41f605e856..c725934f2f 100644 --- a/libraries/networking/src/HTTPResourceRequest.h +++ b/libraries/networking/src/HTTPResourceRequest.h @@ -13,21 +13,20 @@ #define hifi_HTTPResourceRequest_h #include +#include #include #include "ResourceRequest.h" -#include "QUrlAncestry.h" - class HTTPResourceRequest : public ResourceRequest { Q_OBJECT public: HTTPResourceRequest( - const QUrlAncestry& urlAncestry, + const QUrl& url, const bool isObservable = true, const qint64 callerId = -1, const QString& = "" - ) : ResourceRequest(urlAncestry, isObservable, callerId) { } + ) : ResourceRequest(url, isObservable, callerId) { } ~HTTPResourceRequest(); protected: diff --git a/libraries/networking/src/NetworkAccessManager.cpp b/libraries/networking/src/NetworkAccessManager.cpp index c5229d65ac..f73243e675 100644 --- a/libraries/networking/src/NetworkAccessManager.cpp +++ b/libraries/networking/src/NetworkAccessManager.cpp @@ -22,7 +22,7 @@ QNetworkAccessManager& NetworkAccessManager::getInstance() { if (!networkAccessManagers.hasLocalData()) { networkAccessManagers.setLocalData(new QNetworkAccessManager()); } - + return *networkAccessManagers.localData(); } diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 186addbd86..f2fbe41804 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -320,7 +320,7 @@ QVariantList ResourceCache::getResourceList() { return list; } - + void ResourceCache::setRequestLimit(uint32_t limit) { auto sharedItems = DependencyManager::get(); sharedItems->setRequestLimit(limit); @@ -337,7 +337,6 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& QReadLocker locker(&_resourcesLock); resource = _resources.value(url).lock(); } - if (resource) { removeUnusedResource(resource); } @@ -382,7 +381,7 @@ void ResourceCache::addUnusedResource(const QSharedPointer& resource) return; } reserveUnusedResource(resource->getBytes()); - + resource->setLRUKey(++_lastLRUKey); { @@ -411,7 +410,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { _unusedResourcesSize + resourceSize > _unusedResourcesMaxSize) { // unload the oldest resource QMap >::iterator it = _unusedResources.begin(); - + it.value()->setCache(nullptr); auto size = it.value()->getBytes(); @@ -477,7 +476,7 @@ void ResourceCache::updateTotalSize(const qint64& deltaSize) { emit dirty(); } - + QList> ResourceCache::getLoadingRequests() { return DependencyManager::get()->getLoadingRequests(); } @@ -592,7 +591,7 @@ void Resource::refresh() { _request = nullptr; ResourceCache::requestCompleted(_self); } - + _activeUrl = _url; init(); ensureLoading(); @@ -606,7 +605,7 @@ void Resource::allReferencesCleared() { } if (_cache && isCacheable()) { - // create and reinsert new shared pointer + // create and reinsert new shared pointer QSharedPointer self(this, &Resource::deleter); setSelf(self); reinsert(); @@ -631,10 +630,10 @@ void Resource::init(bool resetLoaded) { _loaded = false; } _attempts = 0; - + if (_url.isEmpty()) { _startedLoading = _loaded = true; - + } else if (!(_url.isValid())) { _startedLoading = _failedToLoad = true; } @@ -756,7 +755,7 @@ void Resource::handleReplyFinished() { } else { handleFailedRequest(result); } - + _request->disconnect(this); _request->deleteLater(); _request = nullptr; diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index d9774e3437..9539a10c2d 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -26,7 +26,6 @@ #include "NetworkAccessManager.h" #include "NetworkLogging.h" - ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(atpSupportEnabled) { _thread.setObjectName("Resource Manager Thread"); @@ -125,7 +124,6 @@ ResourceRequest* ResourceManager::createResourceRequest( ResourceRequest* request = nullptr; - qDebug() << "!!!! in createResourceRequest " << callerId; if (scheme == URL_SCHEME_FILE || scheme == URL_SCHEME_QRC) { request = new FileResourceRequest(normalizedURL, isObservable, callerId, extra); } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { @@ -146,7 +144,6 @@ ResourceRequest* ResourceManager::createResourceRequest( QObject::connect(parent, &QObject::destroyed, request, &QObject::deleteLater); } request->moveToThread(&_thread); - return request; } diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index a651e9a2b6..acaf657dfe 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -21,9 +21,8 @@ void ResourceRequest::send() { if (_isObservable) { DependencyManager::get()->update( - _urlAncestry, _callerId, _extra + " => ResourceRequest::send" ); + _url, _callerId, _extra + " => ResourceRequest::send" ); } - if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection); return; diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index 3ce1a9da2b..eb306ca5be 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -18,8 +18,6 @@ #include #include "ByteRange.h" -#include "QUrlAncestry.h" - const QString STAT_ATP_REQUEST_STARTED = "StartedATPRequest"; const QString STAT_HTTP_REQUEST_STARTED = "StartedHTTPRequest"; @@ -46,15 +44,14 @@ public: static const bool IS_NOT_OBSERVABLE = false; ResourceRequest( - const QUrlAncestry& urlAncestry, + const QUrl& url, const bool isObservable = IS_OBSERVABLE, const qint64 callerId = -1, const QString& extra = "" - ) : _urlAncestry(urlAncestry), + ) : _url(url), _isObservable(isObservable), _callerId(callerId), - _extra(extra), - _url(urlAncestry.last()) + _extra(extra) { } virtual ~ResourceRequest() = default; @@ -103,7 +100,6 @@ protected: virtual void doSend() = 0; void recordBytesDownloadedInStats(const QString& statName, int64_t bytesReceived); - QUrl _url; QUrl _relativePathURL; State _state { NotStarted }; @@ -119,7 +115,6 @@ protected: bool _isObservable; qint64 _callerId; QString _extra; - QUrlAncestry _urlAncestry; }; #endif diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index a50438dd54..efddf3c14f 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -669,7 +669,7 @@ OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Oc return args.element; } -bool Octree::readFromFile(const char* fileName, const QUrlAncestry& urlAncestry) { +bool Octree::readFromFile(const char* fileName) { QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS); if (qFileName.endsWith(".json.gz")) { @@ -689,7 +689,7 @@ bool Octree::readFromFile(const char* fileName, const QUrlAncestry& urlAncestry) qCDebug(octree) << "Loading file" << qFileName << "..."; - bool success = readFromStream(fileLength, fileInputStream, "", urlAncestry); + bool success = readFromStream(fileLength, fileInputStream, ""); file.close(); @@ -737,8 +737,7 @@ QString getMarketplaceID(const QString& urlString) { bool Octree::readFromURL( const QString& urlString, const bool isObservable, - const qint64 callerId, - const QUrlAncestry& urlAncestry + const qint64 callerId ) { QString trimmedUrl = urlString.trimmed(); QString marketplaceID = getMarketplaceID(trimmedUrl); @@ -767,19 +766,18 @@ bool Octree::readFromURL( if (wasCompressed) { QDataStream inputStream(uncompressedJsonData); - return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID, urlAncestry); + return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID); } QDataStream inputStream(data); - return readFromStream(data.size(), inputStream, marketplaceID, urlAncestry); + return readFromStream(data.size(), inputStream, marketplaceID); } bool Octree::readFromStream( uint64_t streamLength, QDataStream& inputStream, - const QString& marketplaceID, - const QUrlAncestry& urlAncestry + const QString& marketplaceID ) { // decide if this is binary SVO or JSON-formatted SVO QIODevice *device = inputStream.device(); @@ -792,7 +790,7 @@ bool Octree::readFromStream( return false; } else { qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength; - return readJSONFromStream(streamLength, inputStream, marketplaceID, urlAncestry); + return readJSONFromStream(streamLength, inputStream, marketplaceID); } } @@ -824,8 +822,7 @@ const int READ_JSON_BUFFER_SIZE = 2048; bool Octree::readJSONFromStream( uint64_t streamLength, QDataStream& inputStream, - const QString& marketplaceID, /*=""*/ - const QUrlAncestry& urlAncestry + const QString& marketplaceID /*=""*/ ) { // if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until // we get an eof. Leave streamLength parameter for consistency. @@ -851,7 +848,7 @@ bool Octree::readJSONFromStream( } QVariant asVariant = asDocument.toVariant(); QVariantMap asMap = asVariant.toMap(); - bool success = readFromMap(asMap, urlAncestry); + bool success = readFromMap(asMap); delete[] rawData; return success; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 53acbc5a60..44b429582a 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -29,7 +29,6 @@ #include "OctreePacketData.h" #include "OctreeSceneStats.h" #include "OctreeUtils.h" -#include "QUrlAncestry.h" class ReadBitstreamToTreeParams; class Octree; @@ -210,13 +209,13 @@ public: bool skipThoseWithBadParents) = 0; // Octree importers - bool readFromFile(const char* filename, const QUrlAncestry& urlAncestry = {}); - bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1, const QUrlAncestry& urlAncestry = {}); // will support file urls as well... - bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const QUrlAncestry& urlAncestry = {}); + bool readFromFile(const char* filename); + bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1); // will support file urls as well... + bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); bool readSVOFromStream(uint64_t streamLength, QDataStream& inputStream); - bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const QUrlAncestry& urlAncestry = {}); + bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); bool readJSONFromGzippedFile(QString qFileName); - virtual bool readFromMap(QVariantMap& entityDescription, const QUrlAncestry& urlAncestry = {}) = 0; + virtual bool readFromMap(QVariantMap& entityDescription) = 0; uint64_t getOctreeElementsCount(); diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index eccb812f09..cbcafe9c7d 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -258,29 +258,24 @@ void OffscreenSurface::setMaxFps(uint8_t maxFps) { } void OffscreenSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) { - qDebug() << "Here 1"; loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) { QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem)); }); } void OffscreenSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& callback) { - qDebug() << "Here 2"; loadInternal(qmlSource, createNewContext, nullptr, callback); } void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback, const QmlContextCallback& contextCallback) { - qDebug() << "Here 3"; loadInternal(qmlSource, true, nullptr, callback, contextCallback); } void OffscreenSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& callback) { - qDebug() << "Here 4"; load(qmlSource, false, callback); } void OffscreenSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& callback) { - qDebug() << "Here 5"; return load(QUrl(qmlSourceFile), callback); } diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index 103ed6d232..4e07877d57 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -50,7 +50,7 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool tempDir = zipTemp.path(); path.remove("file:///"); } - + qCDebug(scriptengine) << "Temporary directory at: " + tempDir; if (!isTempDir(tempDir)) { qCDebug(scriptengine) << "Temporary directory mismatch; risk of losing files"; @@ -58,7 +58,7 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool } QStringList fileList = unzipFile(path, tempDir); - + if (!fileList.isEmpty()) { qCDebug(scriptengine) << "First file to upload: " + fileList.first(); } else { diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 297d3bb924..a74d185c6a 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -63,7 +63,7 @@ QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEn QScriptValue XMLHttpRequestClass::getStatus() const { if (_reply) { return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); - } + } return QScriptValue(0); } @@ -144,7 +144,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) { auto accountManager = DependencyManager::get(); - + if (accountManager->hasValidAccessToken()) { static const QString HTTP_AUTHORIZATION_HEADER = "Authorization"; QString bearerString = "Bearer " + accountManager->getAccountInfo().getAccessToken().token; diff --git a/libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h b/libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h deleted file mode 100644 index fd3647b19e..0000000000 --- a/libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// EntityItemWeakPointerWithUrlAncestry.h -// libraries/shared/src/ -// -// Created by Kerry Ivan Kurian 10/15/18 -// 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_EntityItemWeakPointerWithUrlAncestry_h -#define hifi_EntityItemWeakPointerWithUrlAncestry_h - -#include "EntityTypes.h" -#include "QUrlAncestry.h" - - -struct EntityItemWeakPointerWithUrlAncestry { - EntityItemWeakPointerWithUrlAncestry( - const EntityItemWeakPointer& a, - const QUrlAncestry& b - ) : entityItemWeakPointer(a), urlAncestry(b) {} - - EntityItemWeakPointer entityItemWeakPointer; - QUrlAncestry urlAncestry; -}; - - -#endif // hifi_EntityItemWeakPointerWithUrlAncestry_h - diff --git a/libraries/shared/src/QUrlAncestry.cpp b/libraries/shared/src/QUrlAncestry.cpp deleted file mode 100644 index f38c663803..0000000000 --- a/libraries/shared/src/QUrlAncestry.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// QUrlAncestry.cpp -// libraries/shared/src/ -// -// Created by Kerry Ivan Kurian on 10/12/18. -// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "QUrlAncestry.h" - - -QUrlAncestry::QUrlAncestry(const QUrl& resource, const QUrl& referrer) { - this->append(referrer); - this->append(resource); -} - -QUrlAncestry::QUrlAncestry( - const QUrl& resource, - const QUrlAncestry& ancestors) : QVector(ancestors) -{ - this->append(resource); -} - -void QUrlAncestry::toJson(QJsonArray& array) const { - for (auto const& qurl : *this) { - array.append(qurl.toDisplayString()); - } -} - -const QUrl QUrlAncestry::url() const { - return this->last(); -} diff --git a/libraries/shared/src/QUrlAncestry.h b/libraries/shared/src/QUrlAncestry.h deleted file mode 100644 index 84c32ff7c1..0000000000 --- a/libraries/shared/src/QUrlAncestry.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// QUrlAncestry.h -// libraries/shared/src/ -// -// Created by Kerry Ivan Kurian on 10/12/18. -// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. -// -// 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_QUrlAncestry_H -#define hifi_QUrlAncestry_H - -#include -#include -#include - - -class QUrlAncestry : public QVector { -public: - QUrlAncestry() {} - QUrlAncestry(const QUrl& resource, const QUrl& referrer = QUrl("__NONE__")); - QUrlAncestry(const QUrl& resource, const QUrlAncestry& ancestors); - - void toJson(QJsonArray& array) const; - const QUrl url() const; -}; - - -#endif // hifi_QUrlVector_H diff --git a/libraries/shared/src/ResourceRequestObserver.cpp b/libraries/shared/src/ResourceRequestObserver.cpp index 6c52fcdc79..5e0925520a 100644 --- a/libraries/shared/src/ResourceRequestObserver.cpp +++ b/libraries/shared/src/ResourceRequestObserver.cpp @@ -15,22 +15,12 @@ #include #include #include "ResourceRequestObserver.h" -#include "QUrlAncestry.h" - -// void ResourceRequestObserver::update(const QNetworkRequest& request, const qint64 callerId, const QString& extra) { -// update(QUrlAncestry(request.url()), callerId, extra); -// } - -void ResourceRequestObserver::update( - const QUrlAncestry& urlAncestry, +void ResourceRequestObserver::update(const QUrl& requestUrl, const qint64 callerId, - const QString& extra -) { + const QString& extra) { QJsonArray array; - urlAncestry.toJson(array); - QJsonObject data { - { "url", array }, + QJsonObject data { { "url", requestUrl.toString() }, { "callerId", callerId }, { "extra", extra } }; diff --git a/libraries/shared/src/ResourceRequestObserver.h b/libraries/shared/src/ResourceRequestObserver.h index edccdb5e48..1b1bc322e5 100644 --- a/libraries/shared/src/ResourceRequestObserver.h +++ b/libraries/shared/src/ResourceRequestObserver.h @@ -15,7 +15,6 @@ #include #include "DependencyManager.h" -#include "QUrlAncestry.h" class ResourceRequestObserver : public QObject, public Dependency { @@ -23,8 +22,7 @@ class ResourceRequestObserver : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - // void update(const QNetworkRequest& request, const qint64 callerId = -1, const QString& extra = ""); - void update(const QUrlAncestry& urlAncestry, const qint64 callerId = -1, const QString& extra = ""); + void update(const QUrl& requestUrl, const qint64 callerId = -1, const QString& extra = ""); signals: void resourceRequestEvent(QVariantMap result); From bccca94111f175e9d9e5daded7e3e219ee9a9f6e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 13:21:52 -0700 Subject: [PATCH 181/276] Prevent duplicate resources in logs. Thanks to Roxanne! --- libraries/networking/src/ResourceRequest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index acaf657dfe..c63bd4c563 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -19,15 +19,15 @@ void ResourceRequest::send() { - if (_isObservable) { - DependencyManager::get()->update( - _url, _callerId, _extra + " => ResourceRequest::send" ); - } if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection); return; } + if (_isObservable) { + DependencyManager::get()->update(_url, _callerId, _extra + " => ResourceRequest::send"); + } + Q_ASSERT(_state == NotStarted); _state = InProgress; From d44f9e2ccc9b51a4a93a990997f61cc86c4b6be7 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 15:21:37 -0700 Subject: [PATCH 182/276] Tons of improvements --- .../marketplaceItemTester/ItemUnderTest.qml | 25 ++++++--- .../MarketplaceItemTester.qml | 38 +++----------- interface/src/commerce/QmlCommerce.cpp | 25 ++++++--- interface/src/commerce/QmlCommerce.h | 2 +- .../scripting/ClipboardScriptingInterface.cpp | 10 +--- scripts/system/marketplaces/marketplaces.js | 52 ++++++++++++------- 6 files changed, 79 insertions(+), 73 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml index dcb67f3f12..c5aaf20a8f 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -29,7 +29,7 @@ Rectangle { "forward": function(resource, assetType, resourceObjectId){ switch(assetType) { case "application": - Commerce.openApp(resource); + Commerce.installApp(resource, true); break; case "avatar": MyAvatar.useFullAvatarURL(resource); @@ -137,7 +137,7 @@ Rectangle { "entity": hifi.glyphs.wand, "trash": hifi.glyphs.trash, "unknown": hifi.glyphs.circleSlash, - "wearable": hifi.glyphs.hat, + "wearable": hifi.glyphs.hat } property int color: hifi.buttons.blue; property int colorScheme: hifi.colorSchemes.dark; @@ -149,7 +149,18 @@ Rectangle { enabled: comboBox.model[comboBox.currentIndex] !== "unknown" onClicked: { - root.actions["forward"](resource, comboBox.currentText, resourceObjectId); + if (model.currentlyRecordingResources) { + model.currentlyRecordingResources = false; + } else { + model.resourceAccessEventText = ""; + model.currentlyRecordingResources = true; + root.actions["forward"](resource, comboBox.currentText, resourceObjectId); + } + sendToScript({ + method: "tester_updateResourceRecordingStatus", + objectId: resourceListModel.get(index).resourceObjectId, + status: model.currentlyRecordingResources + }); } background: Rectangle { @@ -189,7 +200,7 @@ Rectangle { contentItem: Item { HifiStylesUit.HiFiGlyphs { id: rezIcon; - text: actionButton.glyphs[comboBox.model[comboBox.currentIndex]]; + text: model.currentlyRecordingResources ? hifi.glyphs.scriptStop : actionButton.glyphs[comboBox.model[comboBox.currentIndex]]; anchors.fill: parent size: 30; horizontalAlignment: Text.AlignHCenter; @@ -304,9 +315,9 @@ Rectangle { color: hifi.colors.white text: { if (root.detailsExpanded) { - return resourceAccessEventText + return model.resourceAccessEventText } else { - return (resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." + return (model.resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." } } font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) @@ -340,7 +351,7 @@ Rectangle { anchors.top: detailsTextContainer.bottom anchors.topMargin: 8 anchors.right: parent.right - width: 150 + width: 160 height: 30 text: "Copy to Clipboard" diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 5f2268132c..2a4f2d0e22 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -24,17 +24,13 @@ Rectangle { id: root property string installedApps - property string resourceAccessEventText property var nextResourceObjectId: 0 - property var startDate HifiStylesUit.HifiConstants { id: hifi } ListModel { id: resourceListModel } color: hifi.colors.darkGray - Component.onCompleted: startDate = new Date() - // // TITLE BAR START // @@ -147,8 +143,7 @@ Rectangle { model: resourceListModel spacing: 8 - delegate: ItemUnderTest { - } + delegate: ItemUnderTest { } } Item { @@ -194,8 +189,6 @@ Rectangle { if (resource) { print("!!!! building resource object"); var resourceObj = buildResourceObj(resource); - print("!!!! installing resource object"); - installResourceObj(resourceObj); print("!!!! notifying script of resource object"); sendToScript({ method: 'tester_newResourceObject', @@ -235,6 +228,7 @@ Rectangle { switch (message.method) { case "newResourceObjectInTest": var resourceObject = message.resourceObject; + resourceListModel.clear(); // REMOVE THIS once we support specific referrers resourceListModel.append(resourceObject); spinner.visible = false; break; @@ -244,21 +238,9 @@ Rectangle { spinner.visible = false; break; case "resourceRequestEvent": - try { - var date = new Date(JSON.parse(message.data.date)); - } catch(err) { - print("!!!!! Date conversion failed: " + JSON.stringify(message.data)); - } - // XXX Eventually this date check goes away b/c we will - // be able to match up resouce access events to resource - // object ids, ignoring those that have -1 resource - // object ids. - if (date >= startDate) { - resourceAccessEventText += ( - "[" + date.toISOString() + "] " + - message.data.url.toString().replace("__NONE__,", "") + "\n" - ); - } + // When we support multiple items under test simultaneously, + // we'll have to replace "0" with the correct index. + resourceListModel.setProperty(0, "resourceAccessEventText", message.resourceAccessEventText); break; } } @@ -270,17 +252,13 @@ Rectangle { resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); - return { "resourceObjectId": nextResourceObjectId++, + // Uncomment this once we support more than one item in test at the same time + //nextResourceObjectId++; + return { "resourceObjectId": nextResourceObjectId, "resource": resource, "assetType": assetType }; } - function installResourceObj(resourceObj) { - if ("application" === resourceObj.assetType) { - Commerce.installApp(resourceObj.resource); - } - } - function toUrl(resource) { var httpPattern = /^http/i; return httpPattern.test(resource) ? resource : "file:///" + resource; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 83907df103..ffe89ffc5b 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -315,7 +315,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { return installedAppsFromMarketplace; } -bool QmlCommerce::installApp(const QString& itemHref) { +bool QmlCommerce::installApp(const QString& itemHref, const bool& alsoOpenImmediately) { if (!QDir(_appsPath).exists()) { if (!QDir().mkdir(_appsPath)) { qCDebug(commerce) << "Couldn't make _appsPath directory."; @@ -325,8 +325,8 @@ bool QmlCommerce::installApp(const QString& itemHref) { QUrl appHref(itemHref); - auto request = DependencyManager::get()->createResourceRequest( - this, appHref, true, -1, "QmlCommerce::installApp"); + auto request = + DependencyManager::get()->createResourceRequest(this, appHref, true, -1, "QmlCommerce::installApp"); if (!request) { qCDebug(commerce) << "Couldn't create resource request for app."; @@ -358,13 +358,22 @@ bool QmlCommerce::installApp(const QString& itemHref) { QJsonObject appFileJsonObject = appFileJsonDocument.object(); QString scriptUrl = appFileJsonObject["scriptURL"].toString(); - if ((DependencyManager::get()->loadScript(scriptUrl.trimmed())).isNull()) { - qCDebug(commerce) << "Couldn't load script."; - return false; + // Don't try to re-load (install) a script if it's already running + QStringList runningScripts = DependencyManager::get()->getRunningScripts(); + if (!runningScripts.contains(scriptUrl)) { + if ((DependencyManager::get()->loadScript(scriptUrl.trimmed())).isNull()) { + qCDebug(commerce) << "Couldn't load script."; + return false; + } + + QFileInfo appFileInfo(appFile); + emit appInstalled(appFileInfo.baseName()); + } + + if (alsoOpenImmediately) { + QmlCommerce::openApp(itemHref); } - QFileInfo appFileInfo(appFile); - emit appInstalled(appFileInfo.baseName()); return true; }); request->send(); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index bee30e1b62..2e3c0ec24d 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -88,7 +88,7 @@ protected: Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID); Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = ""); - Q_INVOKABLE bool installApp(const QString& appHref); + Q_INVOKABLE bool installApp(const QString& appHref, const bool& alsoOpenImmediately = false); Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index c14f4ea895..c2d2b69883 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -46,17 +46,11 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, float return retVal; } -bool ClipboardScriptingInterface::importEntities( - const QString& filename, - const bool isObservable, - const qint64 callerId -) { +bool ClipboardScriptingInterface::importEntities(const QString& filename) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "importEntities", Q_RETURN_ARG(bool, retVal), - Q_ARG(const QString&, filename), - Q_ARG(const bool, isObservable), - Q_ARG(const qint64, callerId)); + Q_ARG(const QString&, filename)); return retVal; } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index d59a6b89d5..d6056f83a6 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -52,27 +52,30 @@ var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't writ var resourceRequestEvents = []; function signalResourceRequestEvent(data) { + // Once we can tie resource request events to specific resources, + // we will have to update the "0" in here. + resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " + + data.url.toString().replace("__NONE__,", "") + "\n"; + ui.tablet.sendToQml({ method: "resourceRequestEvent", - data: data }); + data: data, + resourceAccessEventText: resourceObjectsInTest[0].resourceAccessEventText + }); } function onResourceRequestEvent(data) { - var resourceRequestEvent = { - "date": JSON.stringify(new Date()), - "url": data.url, - "callerId": data.callerId, - "extra": data.extra }; - resourceRequestEvents.push(resourceRequestEvent); - signalResourceRequestEvent(resourceRequestEvent); -} - -function pushResourceRequestEvents() { - var length = resourceRequestEvents.length - for (var i = 0; i < length; i++) { - if (i in resourceRequestEvents) { - signalResourceRequestEvent(resourceRequestEvents[i]); - } + // Once we can tie resource request events to specific resources, + // we will have to update the "0" in here. + if (resourceObjectsInTest[0] && resourceObjectsInTest[0].currentlyRecordingResources) { + var resourceRequestEvent = { + "date": new Date(), + "url": data.url, + "callerId": data.callerId, + "extra": data.extra + }; + resourceRequestEvents.push(resourceRequestEvent); + signalResourceRequestEvent(resourceRequestEvent); } } @@ -849,7 +852,8 @@ var resourceObjectsInTest = []; function signalNewResourceObjectInTest(resourceObject) { ui.tablet.sendToQml({ method: "newResourceObjectInTest", - resourceObject: resourceObject }); + resourceObject: resourceObject + }); } var onQmlMessageReceived = function onQmlMessageReceived(message) { @@ -915,6 +919,9 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { break; case 'tester_newResourceObject': var resourceObject = message.resourceObject; + resourceObjectsInTest = []; // REMOVE THIS once we support specific referrers + resourceObject.currentlyRecordingResources = false; + resourceObject.resourceAccessEventText = ""; resourceObjectsInTest[resourceObject.resourceObjectId] = resourceObject; signalNewResourceObjectInTest(resourceObject); break; @@ -924,6 +931,12 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'tester_deleteResourceObject': delete resourceObjectsInTest[message.objectId]; break; + case 'tester_updateResourceRecordingStatus': + resourceObjectsInTest[message.objectId].currentlyRecordingResources = message.status; + if (message.status) { + resourceObjectsInTest[message.objectId].resourceAccessEventText = ""; + } + break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': openMarketplace(message.referrerURL); @@ -1076,7 +1089,9 @@ function pushResourceObjectsInTest() { // that the marketplace item tester QML has heard from us, at least // so that it can indicate to the user that all of the resoruce // objects in test have been transmitted to it. - ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxResourceObjectId + 1 }); + //ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxResourceObjectId + 1 }); + // Since, for now, we only support 1 object in test, always send id: 0 + ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: 0 }); } // Function Name: onTabletScreenChanged() @@ -1165,7 +1180,6 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { // variable amount of time to come up, in practice less than // 750ms. Script.setTimeout(pushResourceObjectsInTest, 750); - Script.setTimeout(pushResourceRequestEvents, 750); } console.debug(ui.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + From de93bbb08bcc0f61722cdb10d216fd38eb98ce01 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 15:43:28 -0700 Subject: [PATCH 183/276] Only show unique resources --- .../marketplaceItemTester/ItemUnderTest.qml | 5 ++-- scripts/system/marketplaces/marketplaces.js | 24 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml index c5aaf20a8f..fcb4eaae43 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -314,10 +314,11 @@ Rectangle { readOnly: true color: hifi.colors.white text: { - if (root.detailsExpanded) { + var numUniqueResources = (model.resourceAccessEventText.split("\n").length - 1); + if (root.detailsExpanded && numUniqueResources > 0) { return model.resourceAccessEventText } else { - return (model.resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." + return numUniqueResources.toString() + " unique resource" + (numUniqueResources === 1 ? "" : "s") + " loaded - expand for details" } } font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index d6056f83a6..487920b764 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -54,14 +54,24 @@ var resourceRequestEvents = []; function signalResourceRequestEvent(data) { // Once we can tie resource request events to specific resources, // we will have to update the "0" in here. - resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " + - data.url.toString().replace("__NONE__,", "") + "\n"; + if (!resourceObjectsInTest[0].resourceUrls) { + resourceObjectsInTest[0].resourceUrls = []; + } - ui.tablet.sendToQml({ - method: "resourceRequestEvent", - data: data, - resourceAccessEventText: resourceObjectsInTest[0].resourceAccessEventText - }); + var resourceUrl = data.url.toString().replace("__NONE__,", ""); + + if (resourceObjectsInTest[0].resourceUrls.indexOf(resourceUrl) === -1) { + resourceObjectsInTest[0].resourceUrls.push(resourceUrl); + + resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " + + resourceUrl + "\n"; + + ui.tablet.sendToQml({ + method: "resourceRequestEvent", + data: data, + resourceAccessEventText: resourceObjectsInTest[0].resourceAccessEventText + }); + } } function onResourceRequestEvent(data) { From f9cc4f5a696c15790ce4c04f4ab89b65cd738fe4 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 16:01:49 -0700 Subject: [PATCH 184/276] Count unique source and url pairs --- .../commerce/marketplaceItemTester/ItemUnderTest.qml | 2 +- scripts/system/marketplaces/marketplaces.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml index fcb4eaae43..27277a28f4 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -318,7 +318,7 @@ Rectangle { if (root.detailsExpanded && numUniqueResources > 0) { return model.resourceAccessEventText } else { - return numUniqueResources.toString() + " unique resource" + (numUniqueResources === 1 ? "" : "s") + " loaded - expand for details" + return numUniqueResources.toString() + " unique source/resource url pair" + (numUniqueResources === 1 ? "" : "s") + " recorded" } } font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 487920b764..59279bada7 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -54,17 +54,17 @@ var resourceRequestEvents = []; function signalResourceRequestEvent(data) { // Once we can tie resource request events to specific resources, // we will have to update the "0" in here. - if (!resourceObjectsInTest[0].resourceUrls) { - resourceObjectsInTest[0].resourceUrls = []; + if (!resourceObjectsInTest[0].resourceDataArray) { + resourceObjectsInTest[0].resourceDataArray = []; } - var resourceUrl = data.url.toString().replace("__NONE__,", ""); + var resourceData = "from: " + data.extra + ": " + data.url.toString().replace("__NONE__,", ""); - if (resourceObjectsInTest[0].resourceUrls.indexOf(resourceUrl) === -1) { - resourceObjectsInTest[0].resourceUrls.push(resourceUrl); + if (resourceObjectsInTest[0].resourceDataArray.indexOf(resourceData) === -1) { + resourceObjectsInTest[0].resourceDataArray.push(resourceData); resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " + - resourceUrl + "\n"; + resourceData + "\n"; ui.tablet.sendToQml({ method: "resourceRequestEvent", From 1023e6d2b9ceec7052b1b39e29b3047d7cbe4210 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 16:03:05 -0700 Subject: [PATCH 185/276] Fix ClipboardScriptingInterface: --- .../src/scripting/ClipboardScriptingInterface.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index c2d2b69883..c14f4ea895 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -46,11 +46,17 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, float return retVal; } -bool ClipboardScriptingInterface::importEntities(const QString& filename) { +bool ClipboardScriptingInterface::importEntities( + const QString& filename, + const bool isObservable, + const qint64 callerId +) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "importEntities", Q_RETURN_ARG(bool, retVal), - Q_ARG(const QString&, filename)); + Q_ARG(const QString&, filename), + Q_ARG(const bool, isObservable), + Q_ARG(const qint64, callerId)); return retVal; } From f5a5c0dad3511fe73782dfd92ea7951fe83797ab Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 16:48:23 -0700 Subject: [PATCH 186/276] Attempt to fix wearable bug? --- scripts/system/marketplaces/marketplaces.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 59279bada7..004f3fcbb8 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -54,10 +54,6 @@ var resourceRequestEvents = []; function signalResourceRequestEvent(data) { // Once we can tie resource request events to specific resources, // we will have to update the "0" in here. - if (!resourceObjectsInTest[0].resourceDataArray) { - resourceObjectsInTest[0].resourceDataArray = []; - } - var resourceData = "from: " + data.extra + ": " + data.url.toString().replace("__NONE__,", ""); if (resourceObjectsInTest[0].resourceDataArray.indexOf(resourceData) === -1) { @@ -933,6 +929,7 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { resourceObject.currentlyRecordingResources = false; resourceObject.resourceAccessEventText = ""; resourceObjectsInTest[resourceObject.resourceObjectId] = resourceObject; + resourceObjectsInTest[resourceObject.resourceObjectId].resourceDataArray = []; signalNewResourceObjectInTest(resourceObject); break; case 'tester_updateResourceObjectAssetType': @@ -944,6 +941,7 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'tester_updateResourceRecordingStatus': resourceObjectsInTest[message.objectId].currentlyRecordingResources = message.status; if (message.status) { + resourceObjectsInTest[message.objectId].resourceDataArray = []; resourceObjectsInTest[message.objectId].resourceAccessEventText = ""; } break; From a1bb68517626d4a31823a9d2782b0ed77d890672 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Mon, 22 Oct 2018 11:04:31 -0700 Subject: [PATCH 187/276] Remove cruft --- libraries/octree/src/Octree.cpp | 2 +- scripts/system/marketplaces/marketplaces.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index efddf3c14f..06db92bcf7 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -689,7 +689,7 @@ bool Octree::readFromFile(const char* fileName) { qCDebug(octree) << "Loading file" << qFileName << "..."; - bool success = readFromStream(fileLength, fileInputStream, ""); + bool success = readFromStream(fileLength, fileInputStream); file.close(); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 004f3fcbb8..3085145176 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -564,7 +564,6 @@ function defaultFor(arg, val) { function rezEntity(itemHref, itemType, marketplaceItemTesterId) { var isWearable = itemType === "wearable"; - print("!!!!! Clipboard.importEntities " + marketplaceItemTesterId); var success = Clipboard.importEntities(itemHref, true, marketplaceItemTesterId); var wearableLocalPosition = null; var wearableLocalRotation = null; @@ -920,7 +919,6 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'checkout_rezClicked': case 'purchases_rezClicked': case 'tester_rezClicked': - print("!!!!! marketplaces tester_rezClicked"); rezEntity(message.itemHref, message.itemType, message.itemId); break; case 'tester_newResourceObject': From a2a6acd45c2d7b5d7a082e84c567ba0ada31802d Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Mon, 22 Oct 2018 14:04:59 -0700 Subject: [PATCH 188/276] Improve code formatting --- libraries/networking/src/ResourceCache.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index f2fbe41804..6f7cad8a04 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -349,7 +349,8 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& resource = createResource( url, fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer(), - extra); resource->setSelf(resource); + extra); + resource->setSelf(resource); resource->setCache(this); resource->moveToThread(qApp->thread()); connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); From e5caf016818854b7b68be61ab2fde98837145d52 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 22 Oct 2018 14:51:54 -0700 Subject: [PATCH 189/276] CR change for body click event --- scripts/system/html/js/entityList.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 2d248e48e6..a4e4c0dcd1 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -200,15 +200,16 @@ function loaded() { elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); } + elFilterTypeSelectBox.onclick = onToggleTypeDropdown; elFilterSearch.onkeyup = refreshEntityList; elFilterSearch.onsearch = refreshEntityList; elFilterInView.onclick = toggleFilterInView; elFilterRadius.onkeyup = onRadiusChange; elFilterRadius.onchange = onRadiusChange; + elFilterRadius.onclick = onRadiusChange; elInfoToggle.onclick = toggleInfo; // create filter type dropdown checkboxes with label and icon for each type - elFilterTypeSelectBox.onclick = toggleTypeDropdown; for (let i = 0; i < FILTER_TYPES.length; ++i) { let type = FILTER_TYPES[i]; let typeFilterID = "filter-type-" + type; @@ -225,11 +226,11 @@ function loaded() { elInput.setAttribute("filterType", type); elInput.checked = true; // all types are checked initially toggleTypeFilter(elInput, false); // add all types to the initial types filter - elInput.onclick = onToggleTypeFilter; elDiv.appendChild(elInput); elLabel.insertBefore(elSpan, elLabel.childNodes[0]); elDiv.appendChild(elLabel); elFilterTypeCheckboxes.appendChild(elDiv); + elDiv.onclick = onToggleTypeFilter; } entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, @@ -654,6 +655,11 @@ function loaded() { elFilterTypeCheckboxes.style.display = isTypeDropdownVisible() ? "none" : "block"; } + function onToggleTypeDropdown(event) { + toggleTypeDropdown(); + event.stopPropagation(); + } + function toggleTypeFilter(elInput, refresh) { let type = elInput.getAttribute("filterType"); let typeChecked = elInput.checked; @@ -679,14 +685,17 @@ function loaded() { } function onToggleTypeFilter(event) { - toggleTypeFilter(this, true); + let elTarget = event.target; + if (elTarget instanceof HTMLInputElement) { + toggleTypeFilter(elTarget, true); + } + event.stopPropagation(); } function onBodyClick(event) { - // if clicking anywhere outside of the type filter dropdown and it's open then close it - let elTarget = event.target; - if (isTypeDropdownVisible() && !elFilterTypeSelectBox.contains(elTarget) && - !elFilterTypeCheckboxes.contains(elTarget)) { + // if clicking anywhere outside of the type filter dropdown (since click event bubbled up to onBodyClick and + // propagation wasn't stopped by onToggleTypeFilter or onToggleTypeDropdown) and the dropdown is open then close it + if (isTypeDropdownVisible()) { toggleTypeDropdown(); } } From e3e197ff5c4515e22b11e5e7d23b0db75583c793 Mon Sep 17 00:00:00 2001 From: Clement Date: Mon, 22 Oct 2018 15:32:29 -0700 Subject: [PATCH 190/276] 2 spaces in cmake files --- cmake/macros/ConfigureCCache.cmake | 54 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/cmake/macros/ConfigureCCache.cmake b/cmake/macros/ConfigureCCache.cmake index 6107faaa21..bec159ef09 100644 --- a/cmake/macros/ConfigureCCache.cmake +++ b/cmake/macros/ConfigureCCache.cmake @@ -10,36 +10,36 @@ # macro(configure_ccache) - find_program(CCACHE_PROGRAM ccache) - if(CCACHE_PROGRAM) - message(STATUS "Configuring ccache") + find_program(CCACHE_PROGRAM ccache) + if(CCACHE_PROGRAM) + message(STATUS "Configuring ccache") - # Set up wrapper scripts - set(C_LAUNCHER "${CCACHE_PROGRAM}") - set(CXX_LAUNCHER "${CCACHE_PROGRAM}") + # Set up wrapper scripts + set(C_LAUNCHER "${CCACHE_PROGRAM}") + set(CXX_LAUNCHER "${CCACHE_PROGRAM}") + + set(LAUNCH_C_IN "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/launch-c.in") + set(LAUNCH_CXX_IN "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/launch-cxx.in") + set(LAUNCH_C "${CMAKE_BINARY_DIR}/CMakeFiles/launch-c") + set(LAUNCH_CXX "${CMAKE_BINARY_DIR}/CMakeFiles/launch-cxx") - set(LAUNCH_C_IN "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/launch-c.in") - set(LAUNCH_CXX_IN "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/launch-cxx.in") - set(LAUNCH_C "${CMAKE_BINARY_DIR}/CMakeFiles/launch-c") - set(LAUNCH_CXX "${CMAKE_BINARY_DIR}/CMakeFiles/launch-cxx") + configure_file(${LAUNCH_C_IN} ${LAUNCH_C}) + configure_file(${LAUNCH_CXX_IN} ${LAUNCH_CXX}) + execute_process(COMMAND chmod a+rx ${LAUNCH_C} ${LAUNCH_CXX}) - configure_file(${LAUNCH_C_IN} ${LAUNCH_C}) - configure_file(${LAUNCH_CXX_IN} ${LAUNCH_CXX}) - execute_process(COMMAND chmod a+rx ${LAUNCH_C} ${LAUNCH_CXX}) - - if(CMAKE_GENERATOR STREQUAL "Xcode") - # Set Xcode project attributes to route compilation and linking - # through our scripts - set(CMAKE_XCODE_ATTRIBUTE_CC ${LAUNCH_C}) - set(CMAKE_XCODE_ATTRIBUTE_CXX ${LAUNCH_CXX}) - set(CMAKE_XCODE_ATTRIBUTE_LD ${LAUNCH_C}) - set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${LAUNCH_CXX}) - else() - # Support Unix Makefiles and Ninja - set(CMAKE_C_COMPILER_LAUNCHER ${LAUNCH_C}) - set(CMAKE_CXX_COMPILER_LAUNCHER ${LAUNCH_CXX}) - endif() + if(CMAKE_GENERATOR STREQUAL "Xcode") + # Set Xcode project attributes to route compilation and linking + # through our scripts + set(CMAKE_XCODE_ATTRIBUTE_CC ${LAUNCH_C}) + set(CMAKE_XCODE_ATTRIBUTE_CXX ${LAUNCH_CXX}) + set(CMAKE_XCODE_ATTRIBUTE_LD ${LAUNCH_C}) + set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS ${LAUNCH_CXX}) else() - message(WARNING "Could not find ccache") + # Support Unix Makefiles and Ninja + set(CMAKE_C_COMPILER_LAUNCHER ${LAUNCH_C}) + set(CMAKE_CXX_COMPILER_LAUNCHER ${LAUNCH_CXX}) endif() + else() + message(WARNING "Could not find ccache") + endif() endmacro() From 3cc78896ac67357638e9f0e670a9adff76bf10f0 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Mon, 22 Oct 2018 15:39:23 -0700 Subject: [PATCH 191/276] Removed duplicate speech control menu item --- interface/src/Menu.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 1464ec6826..91019975cd 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -788,18 +788,6 @@ Menu::Menu() { // Developer > Show Animation Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AnimStats); - // Developer > Scripting > Enable Speech Control API -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) - auto speechRecognizer = DependencyManager::get(); - QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::ControlWithSpeech, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, - speechRecognizer->getEnabled(), - speechRecognizer.data(), - SLOT(setEnabled(bool)), - UNSPECIFIED_POSITION); - connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); -#endif - // Developer > Scripting > Console... addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, DependencyManager::get().data(), From 91f92276864161e9ea93f73014b371b663a1d0b6 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Mon, 22 Oct 2018 15:44:16 -0700 Subject: [PATCH 192/276] Case 19446 - Update via Sandbox Popup -- opens Old Interface --- server-console/src/main.js | 4 ---- server-console/src/modules/hf-notifications.js | 12 ++++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/server-console/src/main.js b/server-console/src/main.js index 5c7913d775..0263f8fd65 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -877,10 +877,6 @@ function onContentLoaded() { hasShownUpdateNotification = true; } }); - notifier.on('click', function(notifierObject, options) { - log.debug("Got click", options.url); - shell.openExternal(options.url); - }); } deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX); diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index b1f337bbc3..e1ea508e47 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -5,6 +5,8 @@ const process = require('process'); const hfApp = require('./hf-app'); const path = require('path'); const AccountInfo = require('./hf-acctinfo').AccountInfo; +const url = require('url'); +const shell = require('electron').shell; const GetBuildInfo = hfApp.getBuildInfo; const buildInfo = GetBuildInfo(); const osType = os.type(); @@ -154,8 +156,14 @@ function HifiNotifications(config, menuNotificationCallback) { var _menuNotificationCallback = menuNotificationCallback; notifier.on('click', function (notifierObject, options) { - StartInterface(options.url); - _menuNotificationCallback(options.notificationType, false); + const optUrl = url.parse(options.url); + if ((optUrl.protocol === "hifi") || (optUrl.protocol === "hifiapp")) { + StartInterface(options.url); + _menuNotificationCallback(options.notificationType, false); + } + else { + shell.openExternal(options.url); + } }); } From d7dce456b269d9bc83c570292d1fb7f35445ff27 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 22 Oct 2018 18:29:27 -0700 Subject: [PATCH 193/276] Fix incorrect default entity properties in Create --- scripts/system/edit.js | 110 +++++++++-------------------------------- 1 file changed, 24 insertions(+), 86 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 71b4b2c54d..a1e8c52d1f 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -299,7 +299,6 @@ function checkEditPermissionsAndUpdate() { const DEFAULT_ENTITY_PROPERTIES = { All: { - collisionless: true, description: "", rotation: { x: 0, y: 0, z: 0, w: 1 }, collidesWith: "static,dynamic,kinematic,otherAvatar", @@ -344,12 +343,22 @@ const DEFAULT_ENTITY_PROPERTIES = { }, Text: { text: "Text", + dimensions: { + x: 0.65, + y: 0.3, + z: 0.01 + }, textColor: { red: 255, green: 255, blue: 255 }, backgroundColor: { red: 0, green: 0, blue: 0 }, lineHeight: 0.06, faceCamera: false, }, Zone: { + dimensions: { + x: 10, + y: 10, + z: 10 + }, flyingAllowed: true, ghostingAllowed: true, filter: "", @@ -359,7 +368,8 @@ const DEFAULT_ENTITY_PROPERTIES = { intensity: 1.0, direction: { x: 0.0, - y: Math.PI / 4, + y: -0.707106769084930, // 45 degrees + z: 0.7071067690849304 }, castShadows: true }, @@ -376,7 +386,7 @@ const DEFAULT_ENTITY_PROPERTIES = { hazeColor: { red: 128, green: 154, - blue: 129 + blue: 179 }, hazeBackgroundBlend: 0, hazeEnableGlare: false, @@ -389,7 +399,6 @@ const DEFAULT_ENTITY_PROPERTIES = { bloomMode: "inherit" }, Model: { - modelURL: "", collisionShape: "none", compoundShapeURL: "", animation: { @@ -413,9 +422,14 @@ const DEFAULT_ENTITY_PROPERTIES = { shapeType: "box", collisionless: true, modelURL: IMAGE_MODEL, - textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }) + textures: JSON.stringify({ "tex.picture": "" }) }, Web: { + dimensions: { + x: 1.6, + y: 0.9, + z: 0.01 + }, sourceUrl: "https://highfidelity.com/", dpi: 30, }, @@ -462,14 +476,15 @@ const DEFAULT_ENTITY_PROPERTIES = { spinFinish: 0, spinSpread: 0, rotateWithEntity: false, - azimuthStart: 0, - azimuthFinish: 0, - polarStart: -Math.PI, - polarFinish: Math.PI + polarStart: 0, + polarFinish: 0, + azimuthStart: -Math.PI, + azimuthFinish: Math.PI }, Light: { color: { red: 255, green: 255, blue: 255 }, intensity: 5.0, + dimensions: DEFAULT_LIGHT_DIMENSIONS, falloffRadius: 1.0, isSpotlight: false, exponent: 1.0, @@ -874,14 +889,12 @@ var toolBar = (function () { addButton("newLightButton", function () { createNewEntity({ type: "Light", - dimensions: DEFAULT_LIGHT_DIMENSIONS, isSpotlight: false, color: { red: 150, green: 150, blue: 150 }, - constantAttenuation: 1, linearAttenuation: 0, quadraticAttenuation: 0, @@ -893,23 +906,6 @@ var toolBar = (function () { addButton("newTextButton", function () { createNewEntity({ type: "Text", - dimensions: { - x: 0.65, - y: 0.3, - z: 0.01 - }, - backgroundColor: { - red: 64, - green: 64, - blue: 64 - }, - textColor: { - red: 255, - green: 255, - blue: 255 - }, - text: "some text", - lineHeight: 0.06 }); }); @@ -922,76 +918,18 @@ var toolBar = (function () { addButton("newWebButton", function () { createNewEntity({ type: "Web", - dimensions: { - x: 1.6, - y: 0.9, - z: 0.01 - }, - sourceUrl: "https://highfidelity.com/" }); }); addButton("newZoneButton", function () { createNewEntity({ type: "Zone", - dimensions: { - x: 10, - y: 10, - z: 10 - } }); }); addButton("newParticleButton", function () { createNewEntity({ type: "ParticleEffect", - isEmitting: true, - emitterShouldTrail: true, - color: { - red: 200, - green: 200, - blue: 200 - }, - colorSpread: { - red: 0, - green: 0, - blue: 0 - }, - colorStart: { - red: 200, - green: 200, - blue: 200 - }, - colorFinish: { - red: 0, - green: 0, - blue: 0 - }, - emitAcceleration: { - x: -0.5, - y: 2.5, - z: -0.5 - }, - accelerationSpread: { - x: 0.5, - y: 1, - z: 0.5 - }, - emitRate: 5.5, - emitSpeed: 0, - speedSpread: 0, - lifespan: 1.5, - maxParticles: 10, - particleRadius: 0.25, - radiusStart: 0, - radiusFinish: 0.1, - radiusSpread: 0, - alpha: 0, - alphaStart: 1, - alphaFinish: 0, - polarStart: 0, - polarFinish: 0, - textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png" }); }); From d5dc93e8002d0349e6248576e841bf5f7dcd3f0f Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 22 Oct 2018 18:31:04 -0700 Subject: [PATCH 194/276] QA fixes and reset server script status --- scripts/system/html/js/entityProperties.js | 95 ++++++++++++---------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d3e31e4b9b..d4202bd6bc 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -52,6 +52,11 @@ const GROUPS = [ propertyID: "id", readOnly: true, }, + { + label: "Description", + type: "string", + propertyID: "description", + }, { label: "Parent", type: "string", @@ -174,21 +179,21 @@ const GROUPS = [ showPropertyRule: { "keyLightMode": "enabled" }, }, { - label: "Light Altitude", - type: "number", - decimals: 2, - unit: "deg", - propertyID: "keyLight.direction.y", - showPropertyRule: { "keyLightMode": "enabled" }, - }, - { - label: "Light Azimuth", + label: "Light Horizontal Angle", type: "number", decimals: 2, unit: "deg", propertyID: "keyLight.direction.x", showPropertyRule: { "keyLightMode": "enabled" }, }, + { + label: "Light Vertical Angle", + type: "number", + decimals: 2, + unit: "deg", + propertyID: "keyLight.direction.y", + showPropertyRule: { "keyLightMode": "enabled" }, + }, { label: "Cast Shadows", type: "bool", @@ -208,18 +213,11 @@ const GROUPS = [ showPropertyRule: { "skyboxMode": "enabled" }, }, { - label: "Skybox URL", + label: "Skybox Source", type: "string", propertyID: "skybox.url", showPropertyRule: { "skyboxMode": "enabled" }, }, - { - type: "buttons", - buttons: [ { id: "copy", label: "Copy URL To Ambient", - className: "black", onClick: copySkyboxURLToAmbientURL } ], - propertyID: "copyURLToAmbient", - showPropertyRule: { "skyboxMode": "enabled" }, - }, { label: "Ambient Light", type: "dropdown", @@ -237,11 +235,18 @@ const GROUPS = [ showPropertyRule: { "ambientLightMode": "enabled" }, }, { - label: "Ambient URL", + label: "Ambient Source", type: "string", propertyID: "ambientLight.ambientURL", showPropertyRule: { "ambientLightMode": "enabled" }, }, + { + type: "buttons", + buttons: [ { id: "copy", label: "Copy from Skybox", + className: "black", onClick: copySkyboxURLToAmbientURL } ], + propertyID: "copyURLToAmbient", + showPropertyRule: { "skyboxMode": "enabled" }, + }, { label: "Haze", type: "dropdown", @@ -395,16 +400,16 @@ const GROUPS = [ type: "bool", propertyID: "animation.running", }, - { - label: "Allow Transition", - type: "bool", - propertyID: "animation.allowTranslation", - }, { label: "Loop", type: "bool", propertyID: "animation.loop", }, + { + label: "Allow Transition", + type: "bool", + propertyID: "animation.allowTranslation", + }, { label: "Hold", type: "bool", @@ -1109,7 +1114,7 @@ const GROUPS = [ column: 1, }, { // below properties having no column number means place them after two columns div - label: "Can cast shadow", + label: "Cast shadows", type: "bool", propertyID: "canCastShadow", }, @@ -1216,18 +1221,18 @@ const GROUPS = [ ]; const GROUPS_PER_TYPE = { - None: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], - Shape: [ 'base', 'shape', 'spatial', 'collision', 'behavior', 'physics' ], - Text: [ 'base', 'text', 'spatial', 'collision', 'behavior', 'physics' ], - Zone: [ 'base', 'zone', 'spatial', 'collision', 'behavior', 'physics' ], - Model: [ 'base', 'model', 'spatial', 'collision', 'behavior', 'physics' ], - Image: [ 'base', 'image', 'spatial', 'collision', 'behavior', 'physics' ], - Web: [ 'base', 'web', 'spatial', 'collision', 'behavior', 'physics' ], - Light: [ 'base', 'light', 'spatial', 'collision', 'behavior', 'physics' ], + None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ], + Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'behavior', 'collision', 'physics' ], + Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ], + Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ], + Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ], + Light: [ 'base', 'light', 'spatial', 'behavior', 'collision', 'physics' ], Material: [ 'base', 'material', 'spatial', 'behavior' ], ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', 'particles_alpha', 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], - Multiple: [ 'base', 'spatial', 'collision', 'behavior', 'physics' ], + Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], }; const EDITOR_TIMEOUT_DURATION = 1500; @@ -1242,6 +1247,15 @@ const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const MATERIAL_PREFIX_STRING = "mat::"; const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; +const NOT_RUNNING_SCRIPT_STATUS = "Not running"; +const ENTITY_SCRIPT_STATUS = { + pending: "Pending", + loading: "Loading", + error_loading_script: "Error loading script", // eslint-disable-line camelcase + error_running_script: "Error running script", // eslint-disable-line camelcase + running: "Running", + unloaded: "Unloaded" +}; const PROPERTY_NAME_DIVISION = { GROUP: 0, @@ -1441,6 +1455,11 @@ function resetProperties() { } } } + + let elServerScriptError = document.getElementById("property-serverScripts-error"); + let elServerScriptStatus = document.getElementById("property-serverScripts-status"); + elServerScriptError.parentElement.style.display = "none"; + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; } function showGroupsForType(type) { @@ -2796,17 +2815,9 @@ function loaded() { if (data.statusRetrieved === false) { elServerScriptStatus.innerText = "Failed to retrieve status"; } else if (data.isRunning) { - var ENTITY_SCRIPT_STATUS = { - pending: "Pending", - loading: "Loading", - error_loading_script: "Error loading script", // eslint-disable-line camelcase - error_running_script: "Error running script", // eslint-disable-line camelcase - running: "Running", - unloaded: "Unloaded" - }; elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status; } else { - elServerScriptStatus.innerText = "Not running"; + elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; } } else if (data.type === "update" && data.selections) { if (data.selections.length === 0) { From 9afb111fbeb5232c56dcc438ea25e7779ea8f71f Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 22 Oct 2018 18:42:06 -0700 Subject: [PATCH 195/276] add link, fix group order --- scripts/system/html/js/entityProperties.js | 187 +++++++++++---------- 1 file changed, 96 insertions(+), 91 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d4202bd6bc..e5bb80ef2c 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -969,6 +969,102 @@ const GROUPS = [ }, ] }, + { + id: "behavior", + label: "BEHAVIOR", + twoColumn: true, + properties: [ + { + label: "Grabbable", + type: "bool", + propertyID: "grab.grabbable", + column: 1, + }, + { + label: "Triggerable", + type: "bool", + propertyID: "grab.triggerable", + column: 2, + }, + { + label: "Cloneable", + type: "bool", + propertyID: "cloneable", + column: 1, + }, + { + label: "Follow Controller", + type: "bool", + propertyID: "grab.grabFollowsController", + column: 2, + }, + { + label: "Clone Lifetime", + type: "number", + unit: "s", + propertyID: "cloneLifetime", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Limit", + type: "number", + propertyID: "cloneLimit", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Dynamic", + type: "bool", + propertyID: "cloneDynamic", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { + label: "Clone Avatar Entity", + type: "bool", + propertyID: "cloneAvatarEntity", + showPropertyRule: { "cloneable": "true" }, + column: 1, + }, + { // below properties having no column number means place them after two columns div + label: "Cast shadows", + type: "bool", + propertyID: "canCastShadow", + }, + { + label: "Link", + type: "string", + propertyID: "href", + }, + { + label: "Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], + propertyID: "script", + }, + { + label: "Server Script", + type: "string", + buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], + propertyID: "serverScripts", + }, + { + label: "Lifetime", + type: "number", + unit: "s", + propertyID: "lifetime", + }, + { + label: "User Data", + type: "textarea", + buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, + { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, + { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], + propertyID: "userData", + }, + ] + }, { id: "collision", label: "COLLISION", @@ -1055,97 +1151,6 @@ const GROUPS = [ }, ] }, - { - id: "behavior", - label: "BEHAVIOR", - twoColumn: true, - properties: [ - { - label: "Grabbable", - type: "bool", - propertyID: "grab.grabbable", - column: 1, - }, - { - label: "Triggerable", - type: "bool", - propertyID: "grab.triggerable", - column: 2, - }, - { - label: "Cloneable", - type: "bool", - propertyID: "cloneable", - column: 1, - }, - { - label: "Follow Controller", - type: "bool", - propertyID: "grab.grabFollowsController", - column: 2, - }, - { - label: "Clone Lifetime", - type: "number", - unit: "s", - propertyID: "cloneLifetime", - showPropertyRule: { "cloneable": "true" }, - column: 1, - }, - { - label: "Clone Limit", - type: "number", - propertyID: "cloneLimit", - showPropertyRule: { "cloneable": "true" }, - column: 1, - }, - { - label: "Clone Dynamic", - type: "bool", - propertyID: "cloneDynamic", - showPropertyRule: { "cloneable": "true" }, - column: 1, - }, - { - label: "Clone Avatar Entity", - type: "bool", - propertyID: "cloneAvatarEntity", - showPropertyRule: { "cloneable": "true" }, - column: 1, - }, - { // below properties having no column number means place them after two columns div - label: "Cast shadows", - type: "bool", - propertyID: "canCastShadow", - }, - { - label: "Script", - type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], - propertyID: "script", - }, - { - label: "Server Script", - type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], - propertyID: "serverScripts", - }, - { - label: "Lifetime", - type: "number", - unit: "s", - propertyID: "lifetime", - }, - { - label: "User Data", - type: "textarea", - buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, - { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], - propertyID: "userData", - }, - ] - }, { id: "physics", label: "PHYSICS", From 8cb09c37eed722dd701afa6b4868581381d03762 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 22 Oct 2018 21:26:30 -0700 Subject: [PATCH 196/276] changed the ui to the settings/controls --- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/ui/PreferencesDialog.cpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index dd40a748af..c55a257d6a 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -533,7 +533,7 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); - + qCDebug(interfaceapp) << "lock " << _lockSitStandState.get() << " sit " << _isInSittingState.get() << " hmd lean "<< _hmdLeanRecenterEnabled; // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 5eccef5e9d..eeca96222b 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -243,6 +243,29 @@ void setupPreferences() { preference->setIndented(true); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->int { if (myAvatar->getUserRecenterModel() == MyAvatar::SitStandModelType::Auto) { + return 0; + } else if (myAvatar->getUserRecenterModel() == MyAvatar::SitStandModelType::ForceSit) { + return 1; + } else { + return 2; + }}; + auto setter = [myAvatar](int value) { if (value == 0) { + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); + } else if (value == 1) { + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); + } else { + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); + }}; + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Disable Recenter", getter, setter); + QStringList items; + items << "Auto" << "Force Sitting" << "Disable Recenter"; + preference->setHeading("User Activity mode"); + preference->setItems(items); + preferences->addPreference(preference); + } + { auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); }; From 3e8663f8e9dbcf53fe86a8a4baf33681b0c8c4d1 Mon Sep 17 00:00:00 2001 From: amantley Date: Mon, 22 Oct 2018 22:31:26 -0700 Subject: [PATCH 197/276] removed the dropdown from the avatar app, because it is in settings controls now --- interface/resources/qml/hifi/AvatarApp.qml | 1 - .../resources/qml/hifi/avatarapp/Settings.qml | 40 +++---------------- scripts/system/avatarapp.js | 14 +------ 3 files changed, 7 insertions(+), 48 deletions(-) diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index d68e5bfcd7..39590748cf 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -252,7 +252,6 @@ Rectangle { var avatarSettings = { dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', collisionsEnabled : settings.avatarCollisionsOn, - userRecenterModel : settings.avatarRecenterModelOn, animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, collisionSoundUrl : settings.avatarCollisionSoundUrl }; diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index ec2b176f54..bad1394133 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -20,7 +20,6 @@ Rectangle { property real scaleValue: scaleSlider.value / 10 property alias dominantHandIsLeft: leftHandRadioButton.checked property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked - property alias avatarRecenterModelOn: userModelComboBox.currentIndex property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text @@ -49,7 +48,6 @@ Rectangle { avatarAnimationJSON = settings.animGraphUrl; avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; avatarCollisionSoundUrl = settings.collisionSoundUrl; - avatarRecenterModelOn = settings.userRecenterModel; visible = true; } @@ -191,7 +189,7 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right - rows: 3 + rows: 2 rowSpacing: 25 columns: 3 @@ -214,7 +212,7 @@ Rectangle { Layout.row: 0 Layout.column: 1 - Layout.leftMargin: 20 + Layout.leftMargin: -40 ButtonGroup.group: leftRight checked: true @@ -231,7 +229,7 @@ Rectangle { Layout.row: 0 Layout.column: 2 - Layout.rightMargin: -20 + Layout.rightMargin: 20 ButtonGroup.group: leftRight @@ -260,7 +258,7 @@ Rectangle { Layout.row: 1 Layout.column: 1 - Layout.leftMargin: 20 + Layout.leftMargin: -40 ButtonGroup.group: onOff colorScheme: hifi.colorSchemes.light @@ -281,7 +279,7 @@ Rectangle { Layout.row: 1 Layout.column: 2 - Layout.rightMargin: -20 + Layout.rightMargin: 20 ButtonGroup.group: onOff colorScheme: hifi.colorSchemes.light @@ -291,34 +289,6 @@ Rectangle { text: "OFF" boxSize: 20 } - - // TextStyle9 - - RalewaySemiBold { - size: 17; - Layout.row: 2 - Layout.column: 0 - - text: "User Model:" - } - - - // sit stand combo box - HifiControlsUit.ComboBox { - Layout.row: 2 - Layout.column: 1 - id: userModelComboBox - comboBox.textRole: "text" - currentIndex: 2 - model: ListModel { - id: cbItems - ListElement { text: "Force Sitting"; color: "Yellow" } - ListElement { text: "Force Standing"; color: "Green" } - ListElement { text: "Auto Mode"; color: "Brown" } - ListElement { text: "Disable Recentering"; color: "Red" } - } - width: 200 - } } ColumnLayout { diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 0c99460929..ece35acce7 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -64,7 +64,6 @@ function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), collisionsEnabled : MyAvatar.getCollisionsEnabled(), - userRecenterModel: MyAvatar.userRecenterModel, collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), @@ -137,14 +136,6 @@ function onCollisionsEnabledChanged(enabled) { } } -function onUserRecenterModelChanged(modelName) { - if (currentAvatarSettings.userRecenterModel !== modelName) { - currentAvatarSettings.userRecenterModel = modelName; - print("emit user recenter model changed"); - sendToQml({ 'method': 'settingChanged', 'name': 'userRecenterModel', 'value': modelName }) - } -} - function onNewCollisionSoundUrl(url) { if(currentAvatarSettings.collisionSoundUrl !== url) { currentAvatarSettings.collisionSoundUrl = url; @@ -320,11 +311,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See case 'saveSettings': MyAvatar.setAvatarScale(message.avatarScale); currentAvatar.avatarScale = message.avatarScale; + MyAvatar.setDominantHand(message.settings.dominantHand); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); - MyAvatar.userRecenterModel = message.settings.userRecenterModel; MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); + settings = getMyAvatarSettings(); break; default: @@ -497,7 +489,6 @@ function off() { MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); - MyAvatar.userRecenterModelChanged.disconnect(onUserRecenterModelChanged); MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); @@ -512,7 +503,6 @@ function on() { MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); - MyAvatar.userRecenterModelChanged.connect(onUserRecenterModelChanged); MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); From 339b25a36290072c9c7f22f24d93a03e55b45fb7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 23 Oct 2018 08:36:18 -0700 Subject: [PATCH 198/276] Fix access to uniforms at index 0 --- libraries/gl/src/gl/GLShaders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/gl/src/gl/GLShaders.cpp b/libraries/gl/src/gl/GLShaders.cpp index b7e80fbeb4..54a386313b 100644 --- a/libraries/gl/src/gl/GLShaders.cpp +++ b/libraries/gl/src/gl/GLShaders.cpp @@ -14,7 +14,7 @@ using namespace gl; void Uniform::load(GLuint glprogram, int index) { this->index = index; - if (index > 0) { + if (index >= 0) { static const GLint NAME_LENGTH = 1024; GLchar glname[NAME_LENGTH]; memset(glname, 0, NAME_LENGTH); From 8ce2081349c56c6925fccb3c4431bb7b54002198 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 10 Sep 2018 15:47:52 -0700 Subject: [PATCH 199/276] Add python discovery --- BUILD.md | 1 + BUILD_LINUX.md | 5 +++++ BUILD_OSX.md | 8 +++++++- BUILD_WIN.md | 10 ++++++++-- cmake/macros/TargetPython.cmake | 22 ++++++++++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 cmake/macros/TargetPython.cmake diff --git a/BUILD.md b/BUILD.md index df3f18cf51..4198c39d1a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -9,6 +9,7 @@ - [cmake](https://cmake.org/download/): 3.9 - [Qt](https://www.qt.io/download-open-source): 5.10.1 +- [Python](https://www.python.org/downloads/): 3.6 or higher - [OpenSSL](https://www.openssl.org/): Use the latest available 1.0 version (**NOT** 1.1) of OpenSSL to avoid security vulnerabilities. - [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 1ee3d2b7c8..15c5915c51 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -40,6 +40,11 @@ Install build tools: sudo apt-get install cmake ``` +Install Python 3: +```bash +sudo apt-get install python3.6 +``` + ### Get code and checkout the tag you need diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 62102b3e18..488c38e909 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -6,6 +6,10 @@ Please read the [general build guide](BUILD.md) for information on dependencies brew install cmake openssl qt +### Python 3 + +Download an install Python 3.6.6 or higher from [here](https://www.python.org/downloads/). Execute the `Update Shell Profile.command` script that is provided with the installer. + ### OpenSSL Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations. @@ -28,7 +32,9 @@ Note that this uses the version from the homebrew formula at the time of this wr If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. - cmake .. -GXcode + cmake .. -G Xcode + +If `cmake` complains about Python 3 being missing, you may need to update your CMake binary with command `brew upgrade cmake`, or by downloading and running the latest CMake installer, depending on how you originally instaled CMake After running cmake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 90d2995e7d..073b048911 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -5,11 +5,17 @@ Note: We are now using Visual Studio 2017 and Qt 5.10.1. If you are upgrading fr Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory. -### Step 1. Visual Studio 2017 +### Step 1. Visual Studio 2017 & Python If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/). -When selecting components, check "Desktop development with C++." Also on the right on the Summary toolbar, check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)". +When selecting components, check "Desktop development with C++". Also on the right on the Summary toolbar, check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)". If you do not already have a python development environment installed, also check "Python Development" in this screen. + +If you already have Visual Studio installed and need to add python, open the "Add or remove programs" control panel and find the "Microsoft Visual Studio Installer". Select it and click "Modify". In the installer, select "Modify" again, then check "Python Development" and allow the installer to apply the changes. + +### Step 1a. Alternate Python + +If you do not wish to use the Python installation bundled with Visual Studio, you can download the installer from [here](https://www.python.org/downloads/). Ensure you get version 3.6.6 or higher. ### Step 2. Installing CMake diff --git a/cmake/macros/TargetPython.cmake b/cmake/macros/TargetPython.cmake new file mode 100644 index 0000000000..a7c3344fc0 --- /dev/null +++ b/cmake/macros/TargetPython.cmake @@ -0,0 +1,22 @@ +macro(TARGET_PYTHON) + if (NOT HIFI_PYTHON_EXEC) + # Find the python interpreter + if (CAME_VERSION VERSION_LESS 3.12) + # this logic is deprecated in CMake after 3.12 + # FIXME eventually we should make 3.12 the min cmake verion and just use the Python3 find_package path + set(Python_ADDITIONAL_VERSIONS 3) + find_package(PythonInterp) + set(HIFI_PYTHON_VERSION ${PYTHON_VERSION_STRING}) + set(HIFI_PYTHON_EXEC ${PYTHON_EXECUTABLE}) + else() + # the new hotness + find_package(Python3) + set(HIFI_PYTHON_VERSION ${Python3_VERSION}) + set(HIFI_PYTHON_EXEC ${Python3_EXECUTABLE}) + endif() + + if ((NOT HIFI_PYTHON_EXEC) OR (HIFI_PYTHON_VERSION VERSION_LESS 3.6)) + message(FATAL_ERROR "Unable to locate Python interpreter 3.6 or higher") + endif() + endif() +endmacro() \ No newline at end of file From c8e664a0a1128015fe83d27390e34b8cf4678f20 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 27 Aug 2018 08:14:37 -0700 Subject: [PATCH 200/276] New SPIRV Shader toolchain --- android/app/build.gradle | 1 - android/build.gradle | 42 +- cmake/externals/glslang/CMakeLists.txt | 42 ++ cmake/externals/spirv_binaries/CMakeLists.txt | 34 ++ cmake/externals/spirv_cross/CMakeLists.txt | 35 ++ cmake/externals/spirv_headers/CMakeLists.txt | 18 + cmake/externals/spirv_tools/CMakeLists.txt | 33 ++ cmake/init.cmake | 4 + cmake/macros/AutoScribeShader.cmake | 283 ++++++++--- cmake/macros/TargetPython.cmake | 4 +- cmake/macros/TargetSPIRV.cmake | 15 + cmake/macros/TargetSpirvBinaries.cmake | 10 + cmake/macros/TargetVulkan.cmake | 19 + libraries/avatars-renderer/CMakeLists.txt | 2 +- .../InterleavedSrgbToLinear.slf | 4 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 57 +-- .../src/display-plugins/SrgbToLinear.slf | 6 +- .../src/RenderableShapeEntityItem.cpp | 6 +- .../entities-renderer/src/paintStroke.slf | 2 +- .../src/paintStroke_fade.slf | 4 +- libraries/entities-renderer/src/polyvox.slf | 8 +- .../entities-renderer/src/polyvox_fade.slf | 8 +- .../src/textured_particle.slf | 2 +- .../src/textured_particle.slv | 2 +- libraries/entities/CMakeLists.txt | 2 +- libraries/gpu-gl-common/CMakeLists.txt | 2 +- .../gpu-gl-common/src/gpu/gl/GLBackend.h | 15 +- .../src/gpu/gl/GLBackendPipeline.cpp | 2 +- .../src/gpu/gl/GLBackendShader.cpp | 156 ++---- .../gpu-gl-common/src/gpu/gl/GLPipeline.cpp | 2 +- libraries/gpu-gl-common/src/gpu/gl/GLShader.h | 42 +- libraries/gpu-gl/CMakeLists.txt | 2 +- libraries/gpu-gl/src/gpu/gl41/GL41Backend.h | 3 +- .../gpu-gl/src/gpu/gl41/GL41BackendShader.cpp | 20 +- libraries/gpu-gl/src/gpu/gl45/GL45Backend.h | 21 +- .../gpu-gl/src/gpu/gl45/GL45BackendShader.cpp | 18 +- libraries/gpu-gles/CMakeLists.txt | 2 +- libraries/gpu-gles/src/gpu/gles/GLESBackend.h | 6 +- .../src/gpu/gles/GLESBackendShader.cpp | 12 - .../src/gpu/gles/GLESBackendTransform.cpp | 3 +- libraries/gpu/src/gpu/DrawColor.slf | 2 +- libraries/gpu/src/gpu/DrawColoredTexture.slf | 4 +- .../gpu/DrawTexcoordRectTransformUnitQuad.slv | 2 +- libraries/gpu/src/gpu/DrawTexture.slf | 2 +- .../gpu/{drawTexture.slp => DrawTexture.slp} | 1 - .../gpu/src/gpu/DrawTextureMirroredX.slf | 2 +- .../gpu/src/gpu/DrawTextureMirroredX.slp | 1 + libraries/gpu/src/gpu/DrawTextureOpaque.slf | 2 +- .../gpu/src/gpu/DrawTransformedTexture.slp | 2 + libraries/gpu/src/gpu/Shader.cpp | 232 +++------ libraries/gpu/src/gpu/Shader.h | 180 +------ libraries/gpu/src/gpu/ShaderConstants.h | 15 - libraries/gpu/src/gpu/Transform.slh | 22 +- libraries/graphics/src/graphics/Light.slh | 8 +- libraries/graphics/src/graphics/Material.slh | 2 +- .../src/graphics/MaterialTextures.slh | 17 +- libraries/graphics/src/graphics/skybox.slf | 4 +- libraries/model-networking/CMakeLists.txt | 2 +- .../procedural/src/procedural/Procedural.cpp | 154 ++---- .../procedural/src/procedural/Procedural.h | 18 +- .../src/procedural/ProceduralCommon.slh | 62 ++- .../src/procedural/ProceduralSkybox.cpp | 2 +- .../src/procedural/ShaderConstants.h | 16 +- .../src/procedural/proceduralSkybox.slf | 12 +- .../src/procedural/proceduralSkybox.slp | 1 + libraries/render-utils/src/Blendshape.slh | 6 +- libraries/render-utils/src/BloomApply.slf | 8 +- libraries/render-utils/src/BloomThreshold.slf | 4 +- .../render-utils/src/DebugDeferredBuffer.cpp | 22 +- .../render-utils/src/DebugDeferredBuffer.h | 6 +- .../render-utils/src/DeferredBufferRead.slh | 18 +- .../src/DeferredLightingEffect.cpp | 2 +- .../render-utils/src/DeferredTransform.slh | 4 +- libraries/render-utils/src/Fade.slh | 6 +- libraries/render-utils/src/Haze.slf | 2 +- libraries/render-utils/src/Haze.slh | 2 +- libraries/render-utils/src/Highlight.slh | 6 +- .../render-utils/src/HighlightEffect.cpp | 37 +- .../render-utils/src/Highlight_aabox.slv | 8 +- libraries/render-utils/src/LightAmbient.slh | 2 +- .../render-utils/src/LightClusterGrid.slh | 6 +- libraries/render-utils/src/LightingModel.slh | 2 +- .../render-utils/src/RenderPipelines.cpp | 36 +- libraries/render-utils/src/ShadingModel.slh | 2 +- libraries/render-utils/src/Shadow.slh | 2 +- libraries/render-utils/src/ShadowCore.slh | 2 +- libraries/render-utils/src/Skinning.slh | 2 +- .../render-utils/src/SubsurfaceScattering.cpp | 12 +- .../render-utils/src/SubsurfaceScattering.h | 1 + .../render-utils/src/SubsurfaceScattering.slh | 6 +- .../render-utils/src/WorkloadResource.slh | 24 +- .../src/debug_deferred_buffer.slf | 12 +- .../render-utils/src/deferred_light_point.slv | 2 +- .../render-utils/src/deferred_light_spot.slv | 2 +- .../render-utils/src/drawWorkloadView.slv | 2 +- libraries/render-utils/src/forward_simple.slf | 59 +-- .../src/forward_simple_textured.slf | 2 +- .../forward_simple_textured_transparent.slf | 2 +- .../src/forward_simple_textured_unlit.slf | 2 +- libraries/render-utils/src/fxaa.slf | 2 +- libraries/render-utils/src/fxaa_blend.slf | 4 +- libraries/render-utils/src/glowLine.slv | 2 +- libraries/render-utils/src/grid.slf | 2 +- libraries/render-utils/src/hmd_ui.slf | 4 +- libraries/render-utils/src/hmd_ui.slv | 2 +- libraries/render-utils/src/parabola.slv | 2 +- .../src/render-utils/ShaderConstants.h | 9 - .../render-utils/debug_deferred_buffer.slp | 0 .../render-utils/src/render-utils/simple.slp | 1 - .../src/render-utils/simpleTranslucent.slp | 2 - .../render-utils/simpleTranslucentUnlit.slp | 2 - .../src/render-utils/simpleUnlit.slp | 2 - .../src/render-utils/simple_transparent.slp | 1 + libraries/render-utils/src/sdf_text3D.slf | 4 +- .../src/sdf_text3D_transparent.slf | 4 +- libraries/render-utils/src/simple.slf | 12 +- libraries/render-utils/src/simple_fade.slf | 110 ----- .../src/simple_opaque_web_browser.slf | 2 +- .../render-utils/src/simple_textured.slf | 2 +- .../render-utils/src/simple_textured_fade.slf | 2 +- .../src/simple_textured_unlit.slf | 2 +- .../src/simple_textured_unlit_fade.slf | 2 +- .../render-utils/src/simple_transparent.slf | 13 +- .../src/simple_transparent_textured.slf | 2 +- .../src/simple_transparent_textured_fade.slf | 2 +- .../src/simple_transparent_textured_unlit.slf | 2 +- ...simple_transparent_textured_unlit_fade.slf | 2 +- .../src/simple_transparent_web_browser.slf | 2 +- libraries/render-utils/src/ssao.slh | 6 +- .../render-utils/src/ssao_debugOcclusion.slf | 2 +- .../render-utils/src/ssao_makePyramid.slf | 2 +- .../render-utils/src/standardDrawTexture.slf | 2 +- .../src/standardDrawTextureNoBlend.slf | 2 +- .../subsurfaceScattering_drawScattering.slf | 18 +- .../src/surfaceGeometry_copyDepth.slf | 2 +- .../surfaceGeometry_downsampleDepthNormal.slf | 4 +- .../src/surfaceGeometry_makeCurvature.slf | 6 +- .../src/surfaceGeometry_makeLinearDepth.slf | 2 +- libraries/render-utils/src/taa.slh | 12 +- libraries/render-utils/src/toneMapping.slf | 4 +- .../src/velocityBuffer_cameraMotion.slf | 2 +- .../render-utils/src/zone_drawSkybox.slf | 4 +- libraries/render/src/render/BlurTask.slh | 8 +- libraries/render/src/render/ShapePipeline.cpp | 43 +- libraries/render/src/render/ShapePipeline.h | 2 +- .../render/src/render/drawItemBounds.slv | 8 +- .../render/src/render/drawItemStatus.slf | 2 +- libraries/script-engine/CMakeLists.txt | 2 +- libraries/shaders/CMakeLists.txt | 13 +- libraries/shaders/ShaderEnums.cpp.in | 18 +- libraries/shaders/headers/310es/header.glsl | 15 + libraries/shaders/headers/410/header.glsl | 15 + libraries/shaders/headers/450/header.glsl | 10 + libraries/shaders/headers/mono.glsl | 0 libraries/shaders/headers/stereo.glsl | 4 + libraries/shaders/src/shaders/Shaders.cpp | 412 +++++++++++++--- libraries/shaders/src/shaders/Shaders.h | 156 +++++- plugins/oculus/CMakeLists.txt | 2 +- plugins/oculusLegacy/CMakeLists.txt | 2 +- plugins/openvr/CMakeLists.txt | 2 +- tests-manual/gpu-textures/CMakeLists.txt | 2 +- .../gpu-textures/src/TestTextures.cpp | 6 +- tests-manual/gpu/CMakeLists.txt | 2 +- tests-manual/render-utils/CMakeLists.txt | 2 +- tests/shaders/CMakeLists.txt | 2 + tests/shaders/src/ShaderTests.cpp | 448 +++++++++++++++--- tests/shaders/src/ShaderTests.h | 5 +- tools/CMakeLists.txt | 3 - tools/ktx-tool/CMakeLists.txt | 2 +- tools/scribe/src/TextTemplate.cpp | 7 + tools/scribe/src/TextTemplate.h | 9 +- tools/scribe/src/main.cpp | 52 +- tools/shadergen.py | 254 ++++++++++ tools/shreflect/CMakeLists.txt | 10 - tools/shreflect/src/main.cpp | 204 -------- 175 files changed, 2316 insertions(+), 1697 deletions(-) create mode 100644 cmake/externals/glslang/CMakeLists.txt create mode 100644 cmake/externals/spirv_binaries/CMakeLists.txt create mode 100644 cmake/externals/spirv_cross/CMakeLists.txt create mode 100644 cmake/externals/spirv_headers/CMakeLists.txt create mode 100644 cmake/externals/spirv_tools/CMakeLists.txt create mode 100644 cmake/macros/TargetSPIRV.cmake create mode 100644 cmake/macros/TargetSpirvBinaries.cmake create mode 100644 cmake/macros/TargetVulkan.cmake rename libraries/gpu/src/gpu/{drawTexture.slp => DrawTexture.slp} (57%) create mode 100644 libraries/gpu/src/gpu/DrawTextureMirroredX.slp create mode 100644 libraries/gpu/src/gpu/DrawTransformedTexture.slp create mode 100644 libraries/procedural/src/procedural/proceduralSkybox.slp create mode 100644 libraries/render-utils/src/render-utils/debug_deferred_buffer.slp delete mode 100644 libraries/render-utils/src/render-utils/simpleTranslucent.slp delete mode 100644 libraries/render-utils/src/render-utils/simpleTranslucentUnlit.slp delete mode 100644 libraries/render-utils/src/render-utils/simpleUnlit.slp create mode 100644 libraries/render-utils/src/render-utils/simple_transparent.slp delete mode 100644 libraries/render-utils/src/simple_fade.slf create mode 100644 libraries/shaders/headers/310es/header.glsl create mode 100644 libraries/shaders/headers/410/header.glsl create mode 100644 libraries/shaders/headers/450/header.glsl create mode 100644 libraries/shaders/headers/mono.glsl create mode 100644 libraries/shaders/headers/stereo.glsl create mode 100644 tools/shadergen.py delete mode 100644 tools/shreflect/CMakeLists.txt delete mode 100644 tools/shreflect/src/main.cpp diff --git a/android/app/build.gradle b/android/app/build.gradle index 76f5acfaea..0136736dc3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -24,7 +24,6 @@ android { '-DANDROID_STL=c++_shared', '-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake', '-DNATIVE_SCRIBE=' + HIFI_ANDROID_PRECOMPILED + '/scribe' + EXEC_SUFFIX, - '-DNATIVE_SHREFLECT=' + HIFI_ANDROID_PRECOMPILED + '/shreflect' + EXEC_SUFFIX, '-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED, '-DRELEASE_NUMBER=' + RELEASE_NUMBER, '-DRELEASE_TYPE=' + RELEASE_TYPE, diff --git a/android/build.gradle b/android/build.gradle index 14f9e4803f..aafb96689e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -161,31 +161,19 @@ def packages = [ ] ] - def scribeLocalFile='scribe' + EXEC_SUFFIX def scribeFile='scribe_linux_x86_64' -def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6' -def scribeVersion='u_iTrJDaE95i2abTPXOpPZckGBIim53G' - -def shreflectLocalFile='shreflect' + EXEC_SUFFIX -def shreflectFile='shreflect_linux_x86_64' -def shreflectChecksum='d6094a8580066c0b6f4e80b5adfb1d98' -def shreflectVersion='jnrpudh6fptIg6T2.Z6fgKP2ultAdKmE' +def scribeChecksum='4635c28192724281d2367ce9e94380ab' +def scribeVersion='mPAY_N846oZH1tPY1bwChB_hzqkiYyoC' if (Os.isFamily(Os.FAMILY_MAC)) { scribeFile = 'scribe_osx_x86_64' - scribeChecksum='72db9d32d4e1e50add755570ac5eb749' - scribeVersion='DAW0DmnjCRib4MD8x93bgc2Z2MpPojZC' - shreflectFile='shreflect_osx_x86_64' - shreflectChecksum='d613ef0703c21371fee93fd2e54b964f' - shreflectVersion='.rYNzjSFq6WtWDnE5KIKRIAGyJtr__ad' + scribeChecksum='1ead61c285d265eba9a5ef91ae3b7c26' + scribeVersion='4TAXWdo9fviw60N2wUA8HNyQ9TabjZa3' } else if (Os.isFamily(Os.FAMILY_WINDOWS)) { scribeFile = 'scribe_win32_x86_64.exe' - scribeChecksum='678e43d290c90fda670c6fefe038a06d' - scribeVersion='PuullrA_bPlO9kXZRt8rLe536X1UI.m7' - shreflectFile='shreflect_win32_x86_64.exe' - shreflectChecksum='6f4a77b8cceb3f1bbc655132c3665060' - shreflectVersion='iIyCyza1nelkbI7ihybF59bBlwrfAC3D' + scribeChecksum='9c29a62595daf4844f95f6744d568c15' + scribeVersion='DUoxjufeX8ZAIVRBKRczWTuZwT13enTv' } def options = [ @@ -461,27 +449,11 @@ task fixScribePermissions(type: Exec, dependsOn: verifyScribe) { commandLine 'chmod', 'a+x', HIFI_ANDROID_PRECOMPILED + '/' + scribeLocalFile } -task downloadShreflect(type: Download) { - src baseUrl + shreflectFile + '?versionId=' + shreflectVersion - dest new File(baseFolder, shreflectLocalFile) - onlyIfNewer true -} - -task verifyShreflect(type: Verify, dependsOn: downloadShreflect) { - src new File(baseFolder, shreflectLocalFile); - checksum shreflectChecksum -} - -task fixShreflectPermissions(type: Exec, dependsOn: verifyShreflect) { - commandLine 'chmod', 'a+x', HIFI_ANDROID_PRECOMPILED + '/' + shreflectLocalFile -} - -task setupScribe(dependsOn: [verifyScribe, verifyShreflect]) { } +task setupScribe(dependsOn: [verifyScribe]) { } // On Windows, we don't need to set the executable bit, but on OSX and Unix we do if (!Os.isFamily(Os.FAMILY_WINDOWS)) { setupScribe.dependsOn fixScribePermissions - setupScribe.dependsOn fixShreflectPermissions } task extractGvrBinaries(dependsOn: extractDependencies) { diff --git a/cmake/externals/glslang/CMakeLists.txt b/cmake/externals/glslang/CMakeLists.txt new file mode 100644 index 0000000000..4f8a6edf0d --- /dev/null +++ b/cmake/externals/glslang/CMakeLists.txt @@ -0,0 +1,42 @@ +set(EXTERNAL_NAME glslang) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +include(ExternalProject) + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://github.com/KhronosGroup/glslang/archive/7.8.2853.zip + URL_MD5 4f93e3818528176c622c137fba05cbf8 + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=-$ + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +# includes +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) +set(SUFFIXED_INSTALL_DIR "${INSTALL_DIR}-$") + +list(APPEND INCLUDE_DIRS ${SUFFIXED_INSTALL_DIR}/include) +#list(APPEND INCLUDE_DIRS ${INSTALL_DIR}/include) +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INCLUDE_DIRS} CACHE PATH "List of glslang include directories") +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIR} CACHE PATH "List of glslang include directories") + + +set(LIB_DIR ${SUFFIXED_INSTALL_DIR}/lib) +list(APPEND LIB_NAMES glslang HLSL OGLCompiler OSDependent SPIRV SPVRemapper) +include(SelectLibraryConfigurations) + +foreach(BASE_LIB ${LIB_NAMES}) + string(TOUPPER ${BASE_LIB} BASE_LIB_UPPER) + list(APPEND ${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${LIB_DIR}/${BASE_LIB}.lib") + list(APPEND ${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG "${LIB_DIR}/${BASE_LIB}d.lib") +endforeach() + +select_library_configurations(${EXTERNAL_NAME_UPPER}) + +set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE FILEPATH "Location of glslang libraries") +set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of glslang libraries") diff --git a/cmake/externals/spirv_binaries/CMakeLists.txt b/cmake/externals/spirv_binaries/CMakeLists.txt new file mode 100644 index 0000000000..d422eb9f16 --- /dev/null +++ b/cmake/externals/spirv_binaries/CMakeLists.txt @@ -0,0 +1,34 @@ +set(EXTERNAL_NAME spirv_binaries) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +include(ExternalProject) +if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/vulkan/vulkansdk-win32-1.1.82.1.tar.gz) + set(DOWNLOAD_MD5 3a83ef490bce248b1a4d6726a3e5893e) + set(BIN_DIR "Bin") +elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/vulkan/vulkansdk-macos-1.1.82.1.tar.gz) + set(DOWNLOAD_MD5 a57d37275b2c5db023ba8e84a63461ff) + set(BIN_DIR "macOS/bin") +else () + set(DOWNLOAD_URL https://public.highfidelity.com/dependencies/vulkan/vulkansdk-linux-x86_64-1.1.82.1.tar.gz) + set(DOWNLOAD_MD5 5a7c9eeda8cee6b36724da7f7cbe5ec6) + set(BIN_DIR "x86_64/bin") +endif () + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ${DOWNLOAD_URL} + URL_MD5 ${DOWNLOAD_MD5} + BUILD_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD ON +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_DIR "${SOURCE_DIR}/${BIN_DIR}" CACHE FILEPATH "SPIRV binary tools location") + diff --git a/cmake/externals/spirv_cross/CMakeLists.txt b/cmake/externals/spirv_cross/CMakeLists.txt new file mode 100644 index 0000000000..eaa7e4ffa1 --- /dev/null +++ b/cmake/externals/spirv_cross/CMakeLists.txt @@ -0,0 +1,35 @@ +set(EXTERNAL_NAME spirv_cross) + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://github.com/KhronosGroup/SPIRV-Cross/archive/2018-08-07.zip + URL_MD5 11198e4dc6a815ffbdb7a0a56d2d9261 + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=-$ ${EXTRA_CMAKE_FLAGS} + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) +set(SUFFIXED_INSTALL_DIR "${INSTALL_DIR}-$") + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SUFFIXED_INSTALL_DIR}/include CACHE PATH "List of Draco include directories") + +if (UNIX) + set(LIB_PREFIX "lib") + set(LIB_EXT "a") +elseif (WIN32) + set(LIB_EXT "lib") +endif () + +foreach(lib glsl msl cpp hlsl reflect util core) + list(APPEND ${EXTERNAL_NAME_UPPER}_LIBRARIES ${SUFFIXED_INSTALL_DIR}/lib/spirv-cross-${lib}.${LIB_EXT}) +endforeach() + +set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Path to SPIRV-Cross libraries") diff --git a/cmake/externals/spirv_headers/CMakeLists.txt b/cmake/externals/spirv_headers/CMakeLists.txt new file mode 100644 index 0000000000..9613f97991 --- /dev/null +++ b/cmake/externals/spirv_headers/CMakeLists.txt @@ -0,0 +1,18 @@ +set(EXTERNAL_NAME spirv_headers) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://github.com/KhronosGroup/SPIRV-Headers/archive/2c512180ca03b5d4f56283efc85745775b45fdc4.zip + URL_MD5 83e652221b5f21d5fdb61c45f5b4d9f9 + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= ${EXTRA_CMAKE_FLAGS} + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) +set(${EXTERNAL_NAME_UPPER}_ROOT ${INSTALL_DIR} CACHE PATH "List of include directories") diff --git a/cmake/externals/spirv_tools/CMakeLists.txt b/cmake/externals/spirv_tools/CMakeLists.txt new file mode 100644 index 0000000000..a58d72502e --- /dev/null +++ b/cmake/externals/spirv_tools/CMakeLists.txt @@ -0,0 +1,33 @@ +set(EXTERNAL_NAME spirv_tools) +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL https://github.com/KhronosGroup/SPIRV-Tools/archive/v2018.4.zip + URL_MD5 7a7c69cf6ff0318910b4bfbdf30bcfc9 + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DSPIRV-Headers_SOURCE_DIR=${SPIRV_HEADERS_ROOT} -DCMAKE_INSTALL_PREFIX:PATH=-$ ${EXTRA_CMAKE_FLAGS} + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) +set(SUFFIXED_INSTALL_DIR "${INSTALL_DIR}-$") + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SUFFIXED_INSTALL_DIR}/include CACHE PATH "List of SPIRV-Tools include directories") + +if (UNIX) + set(LIB_PREFIX "lib") + set(LIB_EXT "a") +elseif (WIN32) + set(LIB_EXT "lib") +endif () + +list(APPEND ${EXTERNAL_NAME_UPPER}_LIBRARIES ${SUFFIXED_INSTALL_DIR}/lib/SPIRV-Tools-opt.${LIB_EXT}) +list(APPEND ${EXTERNAL_NAME_UPPER}_LIBRARIES ${SUFFIXED_INSTALL_DIR}/lib/SPIRV-Tools-link.${LIB_EXT}) +list(APPEND ${EXTERNAL_NAME_UPPER}_LIBRARIES ${SUFFIXED_INSTALL_DIR}/lib/SPIRV-Tools.${LIB_EXT}) +set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Path to SPIRV-Tool libraries") diff --git a/cmake/init.cmake b/cmake/init.cmake index 9adcb167df..3f632b30f8 100644 --- a/cmake/init.cmake +++ b/cmake/init.cmake @@ -10,6 +10,10 @@ if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif () +if (POLICY CMP0074) + cmake_policy(SET CMP0074 OLD) +endif () + set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMakeTargets") # Hide automoc folders (for IDEs) diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index 9c5ad70c78..06c29e06e3 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -8,34 +8,132 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # +# FIXME use the built tools + +macro(AUTOSCRIBE_APPEND_QRC) + string(CONCAT SHADER_QRC "${SHADER_QRC}" "${ARGV1}\n") +endmacro() + +set(VULKAN_DIR $ENV{VULKAN_SDK}) +set(GLSLANG_EXEC "${VULKAN_DIR}/Bin/glslangValidator.exe") +set(SPIRV_CROSS_EXEC "${VULKAN_DIR}/Bin/spirv-cross.exe") +set(SPIRV_OPT_EXEC "${VULKAN_DIR}/Bin/spirv-opt.exe") +set(GLSLC_EXEC "${VULKAN_DIR}/Bin/glslc.exe") +set(SCRIBE_EXEC "D:/scribe.exe") + +macro(AUTOSCRIBE_PLATFORM_SHADER) + set(AUTOSCRIBE_PLATFORM_PATH "${ARGV0}") + string(REGEX MATCH "([0-9]+(es)?)(/stereo)?" PLATFORM_PATH_REGEX ${AUTOSCRIBE_PLATFORM_PATH}) + set(AUTOSCRIBE_DIALECT "${CMAKE_MATCH_1}") + if (CMAKE_MATCH_3) + set(AUTOSCRIBE_VARIANT "stereo") + else() + set(AUTOSCRIBE_VARIANT "mono") + endif() + string(REGEX REPLACE "/" "\\\\" SOURCE_GROUP_PATH ${AUTOSCRIBE_PLATFORM_PATH}) + set(SOURCE_GROUP_PATH "${SHADER_LIB}\\${SOURCE_GROUP_PATH}") + set(AUTOSCRIBE_DIALECT_HEADER "${AUTOSCRIBE_HEADER_DIR}/${AUTOSCRIBE_DIALECT}/header.glsl") + set(AUTOSCRIBE_VARIANT_HEADER "${AUTOSCRIBE_HEADER_DIR}/${AUTOSCRIBE_VARIANT}.glsl") + + set(AUTOSCRIBE_OUTPUT_FILE "${SHADERS_DIR}/${SHADER_LIB}/${AUTOSCRIBE_PLATFORM_PATH}/${SHADER_NAME}.${SHADER_TYPE}") + AUTOSCRIBE_APPEND_QRC("${SHADER_COUNT}/${AUTOSCRIBE_PLATFORM_PATH}/scribe" "${AUTOSCRIBE_OUTPUT_FILE}") + source_group(${SOURCE_GROUP_PATH} FILES ${AUTOSCRIBE_OUTPUT_FILE}) + set_property(SOURCE ${AUTOSCRIBE_OUTPUT_FILE} PROPERTY SKIP_AUTOMOC ON) + list(APPEND SCRIBED_SHADERS ${AUTOSCRIBE_OUTPUT_FILE}) + + set(AUTOSCRIBE_SPIRV_FILE "${AUTOSCRIBE_OUTPUT_FILE}.spv") + # don't add unoptimized spirv to the QRC + #AUTOSCRIBE_APPEND_QRC("${SHADER_COUNT}/${AUTOSCRIBE_PLATFORM_PATH}/spirv_unopt" "${AUTOSCRIBE_SPIRV_FILE}") + source_group(${SOURCE_GROUP_PATH} FILES ${AUTOSCRIBE_SPIRV_FILE}) + set_property(SOURCE ${AUTOSCRIBE_SPIRV_FILE} PROPERTY SKIP_AUTOMOC ON) + list(APPEND SPIRV_SHADERS ${AUTOSCRIBE_SPIRV_FILE}) + + set(AUTOSCRIBE_SPIRV_OPT_FILE "${AUTOSCRIBE_OUTPUT_FILE}.opt.spv") + AUTOSCRIBE_APPEND_QRC("${SHADER_COUNT}/${AUTOSCRIBE_PLATFORM_PATH}/spirv" "${AUTOSCRIBE_SPIRV_OPT_FILE}") + source_group(${SOURCE_GROUP_PATH} FILES ${AUTOSCRIBE_SPIRV_OPT_FILE}) + set_property(SOURCE ${AUTOSCRIBE_SPIRV_OPT_FILE} PROPERTY SKIP_AUTOMOC ON) + list(APPEND SPIRV_SHADERS ${AUTOSCRIBE_SPIRV_OPT_FILE}) + + set(AUTOSCRIBE_SPIRV_GLSL_FILE "${AUTOSCRIBE_OUTPUT_FILE}.glsl") + AUTOSCRIBE_APPEND_QRC("${SHADER_COUNT}/${AUTOSCRIBE_PLATFORM_PATH}/glsl" "${AUTOSCRIBE_SPIRV_GLSL_FILE}") + source_group(${SOURCE_GROUP_PATH} FILES ${AUTOSCRIBE_SPIRV_GLSL_FILE}) + set_property(SOURCE ${AUTOSCRIBE_SPIRV_GLSL_FILE} PROPERTY SKIP_AUTOMOC ON) + list(APPEND SPIRV_SHADERS ${AUTOSCRIBE_SPIRV_GLSL_FILE}) + + set(AUTOSCRIBE_SPIRV_JSON_FILE "${AUTOSCRIBE_OUTPUT_FILE}.json") + AUTOSCRIBE_APPEND_QRC("${SHADER_COUNT}/${AUTOSCRIBE_PLATFORM_PATH}/json" "${AUTOSCRIBE_SPIRV_JSON_FILE}") + source_group(${SOURCE_GROUP_PATH} FILES ${AUTOSCRIBE_SPIRV_JSON_FILE}) + set_property(SOURCE ${AUTOSCRIBE_SPIRV_JSON_FILE} PROPERTY SKIP_AUTOMOC ON) + list(APPEND REFLECTED_SHADERS ${AUTOSCRIBE_SPIRV_JSON_FILE}) + + unset(SHADER_GEN_LINE) + list(APPEND SHADER_GEN_LINE ${AUTOSCRIBE_DIALECT}) + list(APPEND SHADER_GEN_LINE ${AUTOSCRIBE_VARIANT}) + file(RELATIVE_PATH TEMP_PATH ${CMAKE_SOURCE_DIR} ${SHADER_FILE}) + list(APPEND SHADER_GEN_LINE ${TEMP_PATH}) + file(RELATIVE_PATH TEMP_PATH ${CMAKE_SOURCE_DIR} ${AUTOSCRIBE_OUTPUT_FILE}) + list(APPEND SHADER_GEN_LINE ${TEMP_PATH}) + list(APPEND SHADER_GEN_LINE ${AUTOSCRIBE_SHADER_SEEN_LIBS}) + string(CONCAT AUTOSCRIBE_SHADERGEN_COMMANDS "${AUTOSCRIBE_SHADERGEN_COMMANDS}" "${SHADER_GEN_LINE}\n") + + # # FIXME need better mechanism for determining the include files + # add_custom_command( + # OUTPUT ${AUTOSCRIBE_OUTPUT_FILE} + # COMMAND ${SCRIBE_COMMAND} ${SHADER_FILE} ${SCRIBE_ARGS} -o ${AUTOSCRIBE_OUTPUT_FILE} -h ${AUTOSCRIBE_DIALECT_HEADER} -h ${AUTOSCRIBE_VARIANT_HEADER} + # DEPENDS ${SCRIBE_COMMAND} ${SHADER_FILE} ${AUTOSCRIBE_DIALECT_HEADER} ${AUTOSCRIBE_VARIANT_HEADER}) + + # # Generate the spirv file + # add_custom_command( + # OUTPUT ${AUTOSCRIBE_SPIRV_FILE} + # COMMAND ${GLSLANG_EXEC} -V110 -o ${AUTOSCRIBE_SPIRV_FILE} ${AUTOSCRIBE_OUTPUT_FILE} + # DEPENDS ${AUTOSCRIBE_OUTPUT_FILE} ${GLSLANG_EXEC}) + + # # Generate the optimized spirv file + # add_custom_command( + # OUTPUT ${AUTOSCRIBE_SPIRV_OPT_FILE} + # COMMAND ${SPIRV_OPT_EXEC} -O ${AUTOSCRIBE_SPIRV_FILE} -o ${AUTOSCRIBE_SPIRV_OPT_FILE} + # DEPENDS ${AUTOSCRIBE_SPIRV_FILE} ${SPIRV_OPT_EXEC}) + + # # Generate the optimized GLSL file + # add_custom_command( + # OUTPUT ${AUTOSCRIBE_SPIRV_GLSL_FILE} + # COMMAND ${SPIRV_CROSS_EXEC} ${SPIRV_CROSS_ARGS} ${AUTOSCRIBE_SPIRV_OPT_FILE} --output ${AUTOSCRIBE_SPIRV_GLSL_FILE} + # DEPENDS ${AUTOSCRIBE_SPIRV_OPT_FILE} ${SPIRV_CROSS_EXEC}) + + # # Generate the optimized spirv file + # add_custom_command( + # OUTPUT ${AUTOSCRIBE_SPIRV_JSON_FILE} + # COMMAND ${SPIRV_CROSS_EXEC} --reflect json ${AUTOSCRIBE_SPIRV_OPT_FILE} --output ${AUTOSCRIBE_SPIRV_JSON_FILE} + # DEPENDS ${AUTOSCRIBE_SPIRV_OPT_FILE} ${SPIRV_CROSS_EXEC}) +endmacro() + macro(AUTOSCRIBE_SHADER) + # + # Set the include paths + # + # FIXME base the include paths off of output from the scribe tool, + # instead of treating every previously seen shader as a possible header unset(SHADER_INCLUDE_FILES) - # Grab include files foreach(includeFile ${ARGN}) list(APPEND SHADER_INCLUDE_FILES ${includeFile}) endforeach() - foreach(SHADER_INCLUDE ${SHADER_INCLUDE_FILES}) get_filename_component(INCLUDE_DIR ${SHADER_INCLUDE} PATH) list(APPEND SHADER_INCLUDES_PATHS ${INCLUDE_DIR}) endforeach() - - list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS) - #Extract the unique include shader paths set(INCLUDES ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}) foreach(EXTRA_SHADER_INCLUDE ${INCLUDES}) list(APPEND SHADER_INCLUDES_PATHS ${EXTRA_SHADER_INCLUDE}) endforeach() - list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS) - #message(ready for includes ${SHADER_INCLUDES_PATHS}) - - # make the scribe include arguments - set(SCRIBE_INCLUDES) + unset(SCRIBE_INCLUDES) foreach(INCLUDE_PATH ${SHADER_INCLUDES_PATHS}) set(SCRIBE_INCLUDES ${SCRIBE_INCLUDES} -I ${INCLUDE_PATH}/) endforeach() + # + # Figure out the various output names + # # Define the final name of the generated shader file get_filename_component(SHADER_NAME ${SHADER_FILE} NAME_WE) get_filename_component(SHADER_EXT ${SHADER_FILE} EXT) @@ -46,38 +144,36 @@ macro(AUTOSCRIBE_SHADER) elseif(${SHADER_EXT} STREQUAL .slg) set(SHADER_TYPE geom) endif() - file(MAKE_DIRECTORY "${SHADERS_DIR}/${SHADER_LIB}") - set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_LIB}/${SHADER_NAME}.${SHADER_TYPE}") - file(TO_CMAKE_PATH "${SHADER_TARGET}" COMPILED_SHADER) - set(REFLECTED_SHADER "${COMPILED_SHADER}.json") - set(SCRIBE_ARGS -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE}) + set(SCRIBE_ARGS -D GLPROFILE ${GLPROFILE} -T ${SHADER_TYPE} ${SCRIBE_INCLUDES} ) - # Generate the frag/vert file - add_custom_command( - OUTPUT ${SHADER_TARGET} - COMMAND ${SCRIBE_COMMAND} ${SCRIBE_ARGS} - DEPENDS ${SHADER_FILE} ${SCRIBE_COMMAND} ${SHADER_INCLUDE_FILES}) + # SHADER_SCRIBED -> the output of scribe + set(SHADER_SCRIBED "${SHADERS_DIR}/${SHADER_LIB}/${SHADER_NAME}.${SHADER_TYPE}") - # Generate the json reflection - # FIXME move to spirv-cross for this task after we have spirv compatible shaders - add_custom_command( - OUTPUT ${REFLECTED_SHADER} - COMMAND ${SHREFLECT_COMMAND} ${COMPILED_SHADER} - DEPENDS ${SHREFLECT_DEPENDENCY} ${COMPILED_SHADER}) + # SHADER_NAME_FILE -> a file containing the shader name and extension (useful for debugging and for + # determining the type of shader from the filename) + set(SHADER_NAME_FILE "${SHADER_SCRIBED}.name") + file(TO_CMAKE_PATH "${SHADER_SCRIBED}" SHADER_SCRIBED) + file(WRITE "${SHADER_SCRIBED}.name" "${SHADER_NAME}.${SHADER_TYPE}") + AUTOSCRIBE_APPEND_QRC("${SHADER_COUNT}/name" "${SHADER_NAME_FILE}") - #output the generated file name - source_group("Compiled/${SHADER_LIB}" FILES ${COMPILED_SHADER}) - set_property(SOURCE ${COMPILED_SHADER} PROPERTY SKIP_AUTOMOC ON) - list(APPEND COMPILED_SHADERS ${COMPILED_SHADER}) + if (USE_GLES) + set(SPIRV_CROSS_ARGS --version 310es) + AUTOSCRIBE_PLATFORM_SHADER("310es") + AUTOSCRIBE_PLATFORM_SHADER("310es/stereo") + else() + set(SPIRV_CROSS_ARGS --version 410 --no-420pack-extension) + AUTOSCRIBE_PLATFORM_SHADER("410") + AUTOSCRIBE_PLATFORM_SHADER("410/stereo") + if (NOT APPLE) + set(SPIRV_CROSS_ARGS --version 450) + AUTOSCRIBE_PLATFORM_SHADER("450") + AUTOSCRIBE_PLATFORM_SHADER("450/stereo") + endif() + endif() - source_group("Reflected/${SHADER_LIB}" FILES ${REFLECTED_SHADER}) - list(APPEND REFLECTED_SHADERS ${REFLECTED_SHADER}) - - string(CONCAT SHADER_QRC "${SHADER_QRC}" "${COMPILED_SHADER}\n") - string(CONCAT SHADER_QRC "${SHADER_QRC}" "${REFLECTED_SHADER}\n") string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "${SHADER_NAME} = ${SHADER_COUNT},\n") - + string(CONCAT SHADER_SHADERS_ARRAY "${SHADER_SHADERS_ARRAY}" "${SHADER_COUNT},\n") MATH(EXPR SHADER_COUNT "${SHADER_COUNT}+1") endmacro() @@ -86,6 +182,8 @@ macro(AUTOSCRIBE_SHADER_LIB) message(FATAL_ERROR "AUTOSCRIBE_SHADER_LIB can only be used by the shaders library") endif() + file(MAKE_DIRECTORY "${SHADERS_DIR}/${SHADER_LIB}") + list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES "${CMAKE_SOURCE_DIR}/libraries/${SHADER_LIB}/src") string(REGEX REPLACE "[-]" "_" SHADER_NAMESPACE ${SHADER_LIB}) string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "namespace ${SHADER_NAMESPACE} {\n") @@ -165,66 +263,103 @@ macro(AUTOSCRIBE_SHADER_LIB) # Finish the shader enums string(CONCAT SHADER_ENUMS "${SHADER_ENUMS}" "} // namespace ${SHADER_NAMESPACE}\n") - #file(RELATIVE_PATH RELATIVE_LIBRARY_DIR_PATH ${CMAKE_CURRENT_SOURCE_DIR} "${HIFI_LIBRARY_DIR}") - #foreach(HIFI_LIBRARY ${ARGN}) - #list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src) - #endforeach() - #endif() endmacro() macro(AUTOSCRIBE_SHADER_LIBS) - set(SCRIBE_COMMAND scribe) - set(SHREFLECT_COMMAND shreflect) - set(SHREFLECT_DEPENDENCY shreflect) - - # Target dependant Custom rule on the SHADER_FILE - if (ANDROID) - set(GLPROFILE LINUX_GL) - set(SCRIBE_COMMAND ${NATIVE_SCRIBE}) - set(SHREFLECT_COMMAND ${NATIVE_SHREFLECT}) - unset(SHREFLECT_DEPENDENCY) - else() - if (APPLE) - set(GLPROFILE MAC_GL) - elseif(UNIX) - set(GLPROFILE LINUX_GL) - else() - set(GLPROFILE PC_GL) - endif() - endif() - + message(STATUS "Shader processing start") + set(AUTOSCRIBE_HEADER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/headers) # Start the shader IDs - set(SHADER_COUNT 1) set(SHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders") - set(SHADER_ENUMS "") file(MAKE_DIRECTORY ${SHADERS_DIR}) + set(SHADER_ENUMS "") + set(SHADER_COUNT 1) # # Scribe generation & program defintiion # foreach(SHADER_LIB ${ARGN}) + list(APPEND AUTOSCRIBE_SHADER_SEEN_LIBS ${SHADER_LIB}) AUTOSCRIBE_SHADER_LIB(${SHADER_LIB}) endforeach() # Generate the library files configure_file( ShaderEnums.cpp.in - ${CMAKE_CURRENT_BINARY_DIR}/shaders/ShaderEnums.cpp) + ${CMAKE_CURRENT_BINARY_DIR}/ShaderEnums.cpp) configure_file( ShaderEnums.h.in - ${CMAKE_CURRENT_BINARY_DIR}/shaders/ShaderEnums.h) - configure_file( - shaders.qrc.in - ${CMAKE_CURRENT_BINARY_DIR}/shaders.qrc) + ${CMAKE_CURRENT_BINARY_DIR}/ShaderEnums.h) - set(AUTOSCRIBE_SHADER_LIB_SRC "${CMAKE_CURRENT_BINARY_DIR}/shaders/ShaderEnums.h;${CMAKE_CURRENT_BINARY_DIR}/shaders/ShaderEnums.cpp") - set(QT_RESOURCES_FILE ${CMAKE_CURRENT_BINARY_DIR}/shaders.qrc) + configure_file(shaders.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/shaders.qrc) + list(APPEND QT_RESOURCES_FILE ${CMAKE_CURRENT_BINARY_DIR}/shaders.qrc) + + list(APPEND AUTOSCRIBE_SHADER_HEADERS ${AUTOSCRIBE_HEADER_DIR}/mono.glsl ${AUTOSCRIBE_HEADER_DIR}/stereo.glsl) + list(APPEND AUTOSCRIBE_SHADER_HEADERS ${AUTOSCRIBE_HEADER_DIR}/450/header.glsl ${AUTOSCRIBE_HEADER_DIR}/410/header.glsl ${AUTOSCRIBE_HEADER_DIR}/310es/header.glsl) + source_group("Shader Headers" FILES ${AUTOSCRIBE_HEADER_DIR}/mono.glsl ${AUTOSCRIBE_HEADER_DIR}/stereo.glsl) + source_group("Shader Headers\\450" FILES ${AUTOSCRIBE_HEADER_DIR}/450/header.glsl) + source_group("Shader Headers\\410" FILES ${AUTOSCRIBE_HEADER_DIR}/410/header.glsl) + source_group("Shader Headers\\310es" FILES ${AUTOSCRIBE_HEADER_DIR}/310es/header.glsl) + + list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${AUTOSCRIBE_SHADER_HEADERS}) + list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/ShaderEnums.h ${CMAKE_CURRENT_BINARY_DIR}/ShaderEnums.cpp) + # Write the shadergen command list + set(AUTOSCRIBE_SHADERGEN_COMMANDS_FILE ${CMAKE_CURRENT_BINARY_DIR}/shadergen.txt) + file(WRITE ${AUTOSCRIBE_SHADERGEN_COMMANDS_FILE} "${AUTOSCRIBE_SHADERGEN_COMMANDS}") + + # grab the SPIRV binaries we require + # note we don't use the normal ADD_DEPENDENCY_EXTERNAL_PROJECTS macro because only a custom command + # depends on these, not any of our build artifacts, so there's no valid target for the add_dependencies + # call in ADD_DEPENDENCY_EXTERNAL_PROJECTS to use + add_subdirectory(${EXTERNAL_PROJECT_DIR}/spirv_binaries ${EXTERNALS_BINARY_DIR}/spirv_binaries) + + target_python() + + # A custom python script which will generate + if (ANDROID) + add_custom_command( + OUTPUT ${SCRIBED_SHADERS} ${SPIRV_SHADERS} ${REFLECTED_SHADERS} + COMMENT "Generating/updating shaders" + COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_SOURCE_DIR}/tools/shadergen.py + --commands ${AUTOSCRIBE_SHADERGEN_COMMANDS_FILE} + --spirv-binaries ${SPIRV_BINARIES_DIR} + --scribe ${NATIVE_SCRIBE} + --build-dir ${CMAKE_CURRENT_BINARY_DIR} + --source-dir ${CMAKE_SOURCE_DIR} + DEPENDS ${AUTOSCRIBE_SHADER_HEADERS} spirv_binaries ${CMAKE_SOURCE_DIR}/tools/shadergen.py ${ALL_SCRIBE_SHADERS}) + else() + add_custom_command( + OUTPUT ${SCRIBED_SHADERS} ${SPIRV_SHADERS} ${REFLECTED_SHADERS} + COMMENT "Generating/updating shaders" + COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_SOURCE_DIR}/tools/shadergen.py + --commands ${AUTOSCRIBE_SHADERGEN_COMMANDS_FILE} + --spirv-binaries ${SPIRV_BINARIES_DIR} + --scribe $ + --build-dir ${CMAKE_CURRENT_BINARY_DIR} + --source-dir ${CMAKE_SOURCE_DIR} + DEPENDS ${AUTOSCRIBE_SHADER_HEADERS} scribe spirv_binaries ${CMAKE_SOURCE_DIR}/tools/shadergen.py ${ALL_SCRIBE_SHADERS}) + endif() + + add_custom_target(shadergen DEPENDS ${SCRIBED_SHADERS} ${SPIRV_SHADERS} ${REFLECTED_SHADERS}) + set_target_properties(shadergen PROPERTIES FOLDER "Shaders") + # Custom targets required to force generation of the shaders via scribe - add_custom_target(scribe_shaders SOURCES ${ALL_SCRIBE_SHADERS}) - add_custom_target(compiled_shaders SOURCES ${COMPILED_SHADERS}) - add_custom_target(reflected_shaders SOURCES ${REFLECTED_SHADERS}) + add_custom_target(scribe_shaders SOURCES ${ALL_SCRIBE_SHADERS} ${AUTOSCRIBE_SHADER_HEADERS}) set_target_properties(scribe_shaders PROPERTIES FOLDER "Shaders") - set_target_properties(compiled_shaders PROPERTIES FOLDER "Shaders") + + add_custom_target(scribed_shaders SOURCES ${SCRIBED_SHADERS}) + set_target_properties(scribed_shaders PROPERTIES FOLDER "Shaders") + add_dependencies(scribed_shaders shadergen) + + add_custom_target(spirv_shaders SOURCES ${SPIRV_SHADERS}) + set_target_properties(spirv_shaders PROPERTIES FOLDER "Shaders") + add_dependencies(spirv_shaders shadergen) + + add_custom_target(reflected_shaders SOURCES ${REFLECTED_SHADERS}) set_target_properties(reflected_shaders PROPERTIES FOLDER "Shaders") + add_dependencies(reflected_shaders shadergen) + + message(STATUS "Shader processing end") endmacro() + + diff --git a/cmake/macros/TargetPython.cmake b/cmake/macros/TargetPython.cmake index a7c3344fc0..cd0ea0f34c 100644 --- a/cmake/macros/TargetPython.cmake +++ b/cmake/macros/TargetPython.cmake @@ -15,8 +15,8 @@ macro(TARGET_PYTHON) set(HIFI_PYTHON_EXEC ${Python3_EXECUTABLE}) endif() - if ((NOT HIFI_PYTHON_EXEC) OR (HIFI_PYTHON_VERSION VERSION_LESS 3.6)) - message(FATAL_ERROR "Unable to locate Python interpreter 3.6 or higher") + if ((NOT HIFI_PYTHON_EXEC) OR (HIFI_PYTHON_VERSION VERSION_LESS 3.5)) + message(FATAL_ERROR "Unable to locate Python interpreter 3.5 or higher") endif() endif() endmacro() \ No newline at end of file diff --git a/cmake/macros/TargetSPIRV.cmake b/cmake/macros/TargetSPIRV.cmake new file mode 100644 index 0000000000..94c9df9d13 --- /dev/null +++ b/cmake/macros/TargetSPIRV.cmake @@ -0,0 +1,15 @@ +macro(TARGET_SPIRV) + add_dependency_external_projects(spirv_cross) + target_link_libraries(${TARGET_NAME} ${SPIRV_CROSS_LIBRARIES}) + target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${SPIRV_CROSS_INCLUDE_DIRS}) + + # spirv-tools requires spirv-headers + add_dependency_external_projects(spirv_headers) + add_dependency_external_projects(spirv_tools) + target_link_libraries(${TARGET_NAME} ${SPIRV_TOOLS_LIBRARIES}) + target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${SPIRV_TOOLS_INCLUDE_DIRS}) + + add_dependency_external_projects(glslang) + target_link_libraries(${TARGET_NAME} ${GLSLANG_LIBRARIES}) + target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${GLSLANG_INCLUDE_DIRS}) +endmacro() diff --git a/cmake/macros/TargetSpirvBinaries.cmake b/cmake/macros/TargetSpirvBinaries.cmake new file mode 100644 index 0000000000..6cc102d38e --- /dev/null +++ b/cmake/macros/TargetSpirvBinaries.cmake @@ -0,0 +1,10 @@ +# +# Created by Bradley Austin Davis on 2016/02/16 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_SPIRV_BINARIES) + add_dependency_external_projects(spirv_binaries) +endmacro() + diff --git a/cmake/macros/TargetVulkan.cmake b/cmake/macros/TargetVulkan.cmake new file mode 100644 index 0000000000..4702583ff5 --- /dev/null +++ b/cmake/macros/TargetVulkan.cmake @@ -0,0 +1,19 @@ +# +# Created by Bradley Austin Davis on 2016/02/16 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_VULKAN) + find_package(Vulkan) + + if (Vulkan_FOUND) + add_definitions(-DHAVE_VULKAN) + target_include_directories(${TARGET_NAME} PRIVATE ${Vulkan_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${Vulkan_LIBRARIES}) + + add_dependency_external_projects(glslang) + target_include_directories(${TARGET_NAME} PRIVATE ${GLSLANG_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${GLSLANG_LIBRARIES}) + endif() +endmacro() \ No newline at end of file diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index e6b6986e7b..89dcc61805 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME avatars-renderer) setup_hifi_library(Network Script) -link_hifi_libraries(shared gpu graphics animation model-networking script-engine render render-utils image trackers entities-renderer) +link_hifi_libraries(shared shaders gpu graphics animation model-networking script-engine render render-utils image trackers entities-renderer) include_hifi_library_headers(avatars) include_hifi_library_headers(networking) include_hifi_library_headers(fbx) diff --git a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf index 17dedce7f9..e70053dcd9 100644 --- a/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf +++ b/libraries/display-plugins/src/display-plugins/InterleavedSrgbToLinear.slf @@ -2,11 +2,11 @@ struct TextureData { ivec2 textureSize; }; -layout(std140, binding=0) uniform textureDataBuffer { +LAYOUT_STD140(binding=0) uniform textureDataBuffer { TextureData textureData; }; -layout(binding=0) uniform sampler2D colorMap; +LAYOUT(binding=0) uniform sampler2D colorMap; layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 580bea254a..6448c6d3a1 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -363,56 +363,35 @@ void OpenGLDisplayPlugin::customizeContext() { } if (!_presentPipeline) { + gpu::StatePointer blendState = gpu::StatePointer(new gpu::State()); + blendState->setDepthTest(gpu::State::DepthTest(false)); + blendState->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + + gpu::StatePointer scissorState = gpu::StatePointer(new gpu::State()); + scissorState->setDepthTest(gpu::State::DepthTest(false)); + scissorState->setScissorEnable(true); + { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::drawTexture); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false)); - state->setScissorEnable(true); - _simplePipeline = gpu::Pipeline::create(program, state); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture); + _simplePipeline = gpu::Pipeline::create(program, scissorState); + _hudPipeline = gpu::Pipeline::create(program, blendState); } { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::display_plugins::program::SrgbToLinear); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false)); - state->setScissorEnable(true); - _presentPipeline = gpu::Pipeline::create(program, state); + _presentPipeline = gpu::Pipeline::create(program, scissorState); } { - auto vs = gpu::Shader::createVertex(shader::gpu::vertex::DrawUnitQuadTexcoord); - auto ps = gpu::Shader::createPixel(shader::gpu::fragment::DrawTexture); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false)); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _hudPipeline = gpu::Pipeline::create(program, state); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX); + _mirrorHUDPipeline = gpu::Pipeline::create(program, blendState); } { - auto vs = gpu::Shader::createVertex(shader::gpu::vertex::DrawUnitQuadTexcoord); - auto ps = gpu::Shader::createPixel(shader::gpu::fragment::DrawTextureMirroredX); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false)); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _mirrorHUDPipeline = gpu::Pipeline::create(program, state); - } - - { - auto vs = gpu::Shader::createVertex(shader::gpu::vertex::DrawTransformUnitQuad); - auto ps = gpu::Shader::createPixel(shader::gpu::fragment::DrawTexture); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false)); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _cursorPipeline = gpu::Pipeline::create(program, state); + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTransformedTexture); + _cursorPipeline = gpu::Pipeline::create(program, blendState); } } updateCompositeFramebuffer(); diff --git a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf index c2bcfb5cb3..aad9e71e0e 100644 --- a/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf +++ b/libraries/display-plugins/src/display-plugins/SrgbToLinear.slf @@ -1,10 +1,10 @@ // OpenGLDisplayPlugin_present.frag -layout(binding = 0) uniform sampler2D colorMap; +LAYOUT(binding=0) uniform sampler2D colorMap; -layout(location = 0) in vec2 varTexCoord0; +layout(location=0) in vec2 varTexCoord0; -layout(location = 0) out vec4 outFragColor; +layout(location=0) out vec4 outFragColor; float sRGBFloatToLinear(float value) { const float SRGB_ELBOW = 0.04045; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 5003e36e86..a705b61cd3 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -30,12 +30,14 @@ using namespace render::entities; // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. static const float SPHERE_ENTITY_SCALE = 0.5f; +static_assert(shader::render_utils::program::simple != 0, "Validate simple program exists"); +static_assert(shader::render_utils::program::simple_transparent != 0, "Validate simple transparent program exists"); ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { _procedural._vertexSource = gpu::Shader::getVertexShaderSource(shader::render_utils::vertex::simple); // FIXME: Setup proper uniform slots and use correct pipelines for forward rendering - _procedural._opaquefragmentSource = gpu::Shader::getFragmentShaderSource(shader::render_utils::fragment::simple); - _procedural._transparentfragmentSource = gpu::Shader::getFragmentShaderSource(shader::render_utils::fragment::simple_transparent); + _procedural._opaqueFragmentSource = gpu::Shader::Source::get(shader::render_utils::fragment::simple); + _procedural._transparentFragmentSource = gpu::Shader::Source::get(shader::render_utils::fragment::simple_transparent); _procedural._opaqueState->setCullMode(gpu::State::CULL_NONE); _procedural._opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMaskDrawShape(*_procedural._opaqueState); diff --git a/libraries/entities-renderer/src/paintStroke.slf b/libraries/entities-renderer/src/paintStroke.slf index 211685a9ba..ea54637b91 100644 --- a/libraries/entities-renderer/src/paintStroke.slf +++ b/libraries/entities-renderer/src/paintStroke.slf @@ -15,7 +15,7 @@ <@include DeferredBufferWrite.slh@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=0) in vec3 interpolatedNormal; diff --git a/libraries/entities-renderer/src/paintStroke_fade.slf b/libraries/entities-renderer/src/paintStroke_fade.slf index e5f70c8038..1ace04f7b3 100644 --- a/libraries/entities-renderer/src/paintStroke_fade.slf +++ b/libraries/entities-renderer/src/paintStroke_fade.slf @@ -18,7 +18,7 @@ <$declareFadeFragment()$> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=0) in vec3 interpolatedNormal; @@ -30,7 +30,7 @@ struct PolyLineUniforms { vec3 color; }; -layout(binding=0) uniform polyLineBuffer { +LAYOUT(binding=0) uniform polyLineBuffer { PolyLineUniforms polyline; }; diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index 441dfc11e5..6b1aa25a25 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -20,15 +20,15 @@ layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normal; layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _position; 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(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; struct PolyvoxParams { vec4 voxelVolumeSize; }; -layout(binding=0) uniform polyvoxParamsBuffer { +LAYOUT(binding=0) uniform polyvoxParamsBuffer { PolyvoxParams params; }; diff --git a/libraries/entities-renderer/src/polyvox_fade.slf b/libraries/entities-renderer/src/polyvox_fade.slf index 6e236193aa..ae2e05c3dc 100644 --- a/libraries/entities-renderer/src/polyvox_fade.slf +++ b/libraries/entities-renderer/src/polyvox_fade.slf @@ -23,15 +23,15 @@ layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _position; layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _worldPosition; layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _worldFadePosition; -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(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; struct PolyvoxParams { vec4 voxelVolumeSize; }; -layout(binding=0) uniform polyvoxParamsBuffer { +LAYOUT(binding=0) uniform polyvoxParamsBuffer { PolyvoxParams params; }; diff --git a/libraries/entities-renderer/src/textured_particle.slf b/libraries/entities-renderer/src/textured_particle.slf index 7a0cedf011..7dadb6fc49 100644 --- a/libraries/entities-renderer/src/textured_particle.slf +++ b/libraries/entities-renderer/src/textured_particle.slf @@ -10,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -layout(binding=0) uniform sampler2D colorMap; +LAYOUT(binding=0) uniform sampler2D colorMap; layout(location=0) in vec4 varColor; layout(location=1) in vec2 varTexcoord; diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index 3eacaec3b5..4d17fe132b 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -43,7 +43,7 @@ struct ParticleUniforms { vec2 spare; }; -layout(std140, binding=0) uniform particleBuffer { +LAYOUT_STD140(binding=0) uniform particleBuffer { ParticleUniforms particle; }; diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index dca495ee03..c547708ffa 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -5,4 +5,4 @@ include_hifi_library_headers(fbx) include_hifi_library_headers(gpu) include_hifi_library_headers(image) include_hifi_library_headers(ktx) -link_hifi_libraries(shared networking octree avatars graphics model-networking) \ No newline at end of file +link_hifi_libraries(shared shaders networking octree avatars graphics model-networking) \ No newline at end of file diff --git a/libraries/gpu-gl-common/CMakeLists.txt b/libraries/gpu-gl-common/CMakeLists.txt index 2b6f8d4d40..70cf3536ed 100644 --- a/libraries/gpu-gl-common/CMakeLists.txt +++ b/libraries/gpu-gl-common/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME gpu-gl-common) setup_hifi_library(Concurrent) -link_hifi_libraries(shared gl gpu) +link_hifi_libraries(shared gl gpu shaders) GroupSources("src") target_opengl() diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 267c2a97ad..c309bcb864 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -643,18 +643,21 @@ protected: } } _pipeline; - // Backend dependant compilation of the shader + // Backend dependent compilation of the shader virtual void postLinkProgram(ShaderObject& programObject, const Shader& program) const; virtual GLShader* compileBackendProgram(const Shader& program, const Shader::CompilationHandler& handler); virtual GLShader* compileBackendShader(const Shader& shader, const Shader::CompilationHandler& handler); - virtual std::string getBackendShaderHeader() const = 0; - // For a program, this will return a string containing all the source files (without any - // backend headers or defines). For a vertex, fragment or geometry shader, this will - // return the fully customized shader with all the version and backend specific + + // For a program, this will return a string containing all the source files (without any + // backend headers or defines). For a vertex, fragment or geometry shader, this will + // return the fully customized shader with all the version and backend specific // preprocessor directives // The program string returned can be used as a key for a cache of shader binaries // The shader strings can be reliably sent to the low level `compileShader` functions - virtual std::string getShaderSource(const Shader& shader, int version) final; + virtual std::string getShaderSource(const Shader& shader, shader::Variant version) final; + shader::Variant getShaderVariant() const { return isStereo() ? shader::Variant::Stereo : shader::Variant::Mono; } + virtual shader::Dialect getShaderDialect() const = 0; + class ElementResource { public: gpu::Element _element; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index 7a06b3af86..1e811653f9 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -54,7 +54,7 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { // check the program cache // pick the program version #ifdef GPU_STEREO_CAMERA_BUFFER - GLuint glprogram = pipelineObject->_program->getProgram((GLShader::Version)isStereo()); + GLuint glprogram = pipelineObject->_program->getProgram(getShaderVariant()); #else GLuint glprogram = pipelineObject->_program->getProgram(); #endif diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp index 7267e29be2..f737842ec0 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendShader.cpp @@ -25,120 +25,53 @@ static const std::array SHADER_DOMAINS{ { GL_GEOMETRY_SHADER, } }; -// Domain specific defines -// Must match the order of type specified in gpu::Shader::Type -static const std::array DOMAIN_DEFINES{ { - "#define GPU_VERTEX_SHADER", - "#define GPU_PIXEL_SHADER", - "#define GPU_GEOMETRY_SHADER", -} }; - -// Stereo specific defines -static const std::string stereoVersion{ -#ifdef GPU_STEREO_DRAWCALL_INSTANCED -R"SHADER( -#define GPU_TRANSFORM_IS_STEREO -#define GPU_TRANSFORM_STEREO_CAMERA -#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED -#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN -)SHADER" -#endif -#ifdef GPU_STEREO_DRAWCALL_DOUBLED -#ifdef GPU_STEREO_CAMERA_BUFFER -R"SHADER( -#define GPU_TRANSFORM_IS_STEREO -#define GPU_TRANSFORM_STEREO_CAMERA -#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED -)SHADER" -#else -R"SHADER( -#define GPU_TRANSFORM_IS_STEREO -)SHADER" -#endif -#endif -}; - -// TextureTable specific defines -static const std::string textureTableVersion { - "#extension GL_ARB_bindless_texture : require\n#define GPU_TEXTURE_TABLE_BINDLESS\n" -}; - -// Versions specific of the shader -static const std::array VERSION_DEFINES { { - "", - stereoVersion -} }; - -static std::string getShaderTypeString(Shader::Type type) { - switch (type) { - case Shader::Type::VERTEX: - return "vertex"; - case Shader::Type::PIXEL: - return "pixel"; - case Shader::Type::GEOMETRY: - return "geometry"; - case Shader::Type::PROGRAM: - return "program"; - default: - qFatal("Unexpected shader type %d", type); - Q_UNREACHABLE(); - } -} - -std::string GLBackend::getShaderSource(const Shader& shader, int version) { +std::string GLBackend::getShaderSource(const Shader& shader, shader::Variant variant) { if (shader.isProgram()) { std::string result; - result.append("// VERSION " + std::to_string(version)); for (const auto& subShader : shader.getShaders()) { - result.append("//-------- "); - result.append(getShaderTypeString(subShader->getType())); - result.append("\n"); - result.append(subShader->getSource().getCode()); + if (subShader) { + result += subShader->getSource().getSource(getShaderDialect(), variant); + } } return result; - } - - std::string shaderDefines = getBackendShaderHeader() + "\n" - + (supportsBindless() ? textureTableVersion : "\n") - + DOMAIN_DEFINES[shader.getType()] + "\n" - + VERSION_DEFINES[version]; - - return shaderDefines + "\n" + shader.getSource().getCode(); + } + return shader.getSource().getSource(getShaderDialect(), variant); } GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::CompilationHandler& handler) { // Any GLSLprogram ? normally yes... GLenum shaderDomain = SHADER_DOMAINS[shader.getType()]; GLShader::ShaderObjects shaderObjects; - Shader::CompilationLogs compilationLogs(GLShader::NumVersions); + const auto& variants = shader::allVariants(); + Shader::CompilationLogs compilationLogs(variants.size()); shader.incrementCompilationAttempt(); - - for (int version = 0; version < GLShader::NumVersions; version++) { - auto& shaderObject = shaderObjects[version]; - auto shaderSource = getShaderSource(shader, version); + for (const auto& variant : variants) { + auto index = static_cast(variant); + auto shaderSource = getShaderSource(shader, variant); + auto& shaderObject = shaderObjects[index]; if (handler) { bool retest = true; std::string currentSrc = shaderSource; // When a Handler is specified, we can try multiple times to build the shader and let the handler change the source if the compilation fails. - // The retest bool is set to false as soon as the compilation succeed to wexit the while loop. + // The retest bool is set to false as soon as the compilation succeed to exit the while loop. // The handler tells us if we should retry or not while returning a modified version of the source. while (retest) { - bool result = ::gl::compileShader(shaderDomain, currentSrc, shaderObject.glshader, compilationLogs[version].message); - compilationLogs[version].compiled = result; + bool result = ::gl::compileShader(shaderDomain, currentSrc, shaderObject.glshader, compilationLogs[index].message); + compilationLogs[index].compiled = result; if (!result) { std::string newSrc; - retest = handler(shader, currentSrc, compilationLogs[version], newSrc); + retest = handler(shader, currentSrc, compilationLogs[index], newSrc); currentSrc = newSrc; } else { retest = false; } } } else { - compilationLogs[version].compiled = ::gl::compileShader(shaderDomain, shaderSource, shaderObject.glshader, compilationLogs[version].message); + compilationLogs[index].compiled = ::gl::compileShader(shaderDomain, shaderSource, shaderObject.glshader, compilationLogs[index].message); } - if (!compilationLogs[version].compiled) { - qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Shader didn't compile:\n" << compilationLogs[version].message.c_str(); + if (!compilationLogs[index].compiled) { + qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Shader didn't compile:\n" << compilationLogs[index].message.c_str(); shader.setCompilationLogs(compilationLogs); return nullptr; } @@ -162,11 +95,13 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: GLShader::ShaderObjects programObjects; program.incrementCompilationAttempt(); - Shader::CompilationLogs compilationLogs(GLShader::NumVersions); + const auto& variants = shader::allVariants(); + Shader::CompilationLogs compilationLogs(variants.size()); - for (int version = 0; version < GLShader::NumVersions; version++) { - auto& programObject = programObjects[version]; - auto programSource = getShaderSource(program, version); + for (const auto& variant : variants) { + auto index = static_cast(variant); + auto& programObject = programObjects[index]; + auto programSource = getShaderSource(program, variant); auto hash = ::gl::getShaderHash(programSource); CachedShader cachedBinary; @@ -199,11 +134,11 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: for (auto subShader : program.getShaders()) { auto object = GLShader::sync((*this), *subShader, handler); if (object) { - shaderGLObjects.push_back(object->_shaderObjects[version].glshader); + shaderGLObjects.push_back(object->_shaderObjects[index].glshader); } else { qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - One of the shaders of the program is not compiled?"; - compilationLogs[version].compiled = false; - compilationLogs[version].message = std::string("Failed to compile, one of the shaders of the program is not compiled ?"); + compilationLogs[index].compiled = false; + compilationLogs[index].message = std::string("Failed to compile, one of the shaders of the program is not compiled ?"); program.setCompilationLogs(compilationLogs); return nullptr; } @@ -211,9 +146,9 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: glprogram = ::gl::buildProgram(shaderGLObjects); - if (!::gl::linkProgram(glprogram, compilationLogs[version].message)) { - qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Program didn't link:\n" << compilationLogs[version].message.c_str(); - compilationLogs[version].compiled = false; + if (!::gl::linkProgram(glprogram, compilationLogs[index].message)) { + qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Program didn't link:\n" << compilationLogs[index].message.c_str(); + compilationLogs[index].compiled = false; glDeleteProgram(glprogram); glprogram = 0; return nullptr; @@ -228,12 +163,12 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: } if (glprogram == 0) { - qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Program didn't link:\n" << compilationLogs[version].message.c_str(); + qCWarning(gpugllogging) << "GLBackend::compileBackendProgram - Program didn't link:\n" << compilationLogs[index].message.c_str(); program.setCompilationLogs(compilationLogs); return nullptr; } - compilationLogs[version].compiled = true; + compilationLogs[index].compiled = true; programObject.glprogram = glprogram; postLinkProgram(programObject, program); } @@ -249,7 +184,10 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader:: static const GLint INVALID_UNIFORM_INDEX = -1; GLint GLBackend::getRealUniformLocation(GLint location) const { - auto& shader = _pipeline._programShader->_shaderObjects[(GLShader::Version)isStereo()]; + auto variant = isStereo() ? shader::Variant::Stereo : shader::Variant::Mono; + auto index = static_cast(variant); + + auto& shader = _pipeline._programShader->_shaderObjects[index]; auto itr = shader.uniformRemap.find(location); if (itr == shader.uniformRemap.end()) { // This shouldn't happen, because we use reflection to determine all the possible @@ -264,20 +202,23 @@ GLint GLBackend::getRealUniformLocation(GLint location) const { void GLBackend::postLinkProgram(ShaderObject& shaderObject, const Shader& program) const { const auto& glprogram = shaderObject.glprogram; - const auto& expectedUniforms = program.getUniforms(); - const auto expectedLocationsByName = expectedUniforms.getLocationsByName(); - const auto uniforms = ::gl::Uniform::load(glprogram, expectedUniforms.getNames()); - auto& uniformRemap = shaderObject.uniformRemap; + const auto& expectedUniforms = program.getReflection().uniforms; - // Pre-initialize all the uniforms with an invalid location - for (const auto& entry : expectedLocationsByName) { + auto& uniformRemap = shaderObject.uniformRemap; + // initialize all the uniforms with an invalid location + for (const auto& entry : expectedUniforms) { uniformRemap[entry.second] = INVALID_UNIFORM_INDEX; } - // Now load up all the actual found uniform location + + // Get the actual uniform locations from the shader + const auto names = Shader::Reflection::getNames(expectedUniforms); + const auto uniforms = ::gl::Uniform::load(glprogram, names); + + // Now populate the remapping with the found locations for (const auto& uniform : uniforms) { const auto& name = uniform.name; - const auto& expectedLocation = expectedLocationsByName.at(name); + const auto& expectedLocation = expectedUniforms.at(name); const auto& location = uniform.binding; uniformRemap[expectedLocation] = location; } @@ -462,3 +403,4 @@ void GLBackend::initShaderBinaryCache() { void GLBackend::killShaderBinaryCache() { ::gl::saveShaderCache(_shaderBinaryCache._binaries); } + diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp index a099e6e66a..e00dc9fc25 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLPipeline.cpp @@ -52,7 +52,7 @@ GLPipeline* GLPipeline::sync(GLBackend& backend, const Pipeline& pipeline) { // Special case for view correction matrices, any pipeline that declares the correction buffer // uniform will automatically have it provided without any client code necessary. // Required for stable lighting in the HMD. - object->_cameraCorrection = shader->getUniformBuffers().isValid(gpu::slot::buffer::CameraCorrection); + object->_cameraCorrection = shader->getReflection().validUniformBuffer(gpu::slot::buffer::CameraCorrection); object->_program = programObject; object->_state = stateObject; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLShader.h b/libraries/gpu-gl-common/src/gpu/gl/GLShader.h index 5d5d8a4a3c..1d56bb2122 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLShader.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLShader.h @@ -14,43 +14,45 @@ namespace gpu { namespace gl { struct ShaderObject { - GLuint glshader { 0 }; - GLuint glprogram { 0 }; + enum class BindingType + { + INPUT, + OUTPUT, + TEXTURE, + SAMPLER, + UNIFORM_BUFFER, + RESOURCE_BUFFER, + UNIFORM, + }; - using LocationMap = std::unordered_map ; - LocationMap uniformRemap; + using LocationMap = std::unordered_map; + using ReflectionMap = std::map; + using UniformMap = std::unordered_map; + + GLuint glshader{ 0 }; + GLuint glprogram{ 0 }; + + UniformMap uniformRemap; }; class GLShader : public GPUObject { public: static GLShader* sync(GLBackend& backend, const Shader& shader, const Shader::CompilationHandler& handler = nullptr); - - enum Version { - Mono = 0, - Stereo, - - NumVersions - }; - using ShaderObject = gpu::gl::ShaderObject; - using ShaderObjects = std::array< ShaderObject, NumVersions >; - - using UniformMapping = std::map; - using UniformMappingVersions = std::vector; + using ShaderObjects = std::array; GLShader(const std::weak_ptr& backend); ~GLShader(); ShaderObjects _shaderObjects; - GLuint getProgram(Version version = Mono) const { - return _shaderObjects[version].glprogram; + GLuint getProgram(shader::Variant version = shader::Variant::Mono) const { + return _shaderObjects[static_cast(version)].glprogram; } const std::weak_ptr _backend; }; -} } - +}} // namespace gpu::gl #endif diff --git a/libraries/gpu-gl/CMakeLists.txt b/libraries/gpu-gl/CMakeLists.txt index faddab8531..225c795754 100644 --- a/libraries/gpu-gl/CMakeLists.txt +++ b/libraries/gpu-gl/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME gpu-gl) setup_hifi_library(Concurrent) -link_hifi_libraries(shared gl gpu gpu-gl-common) +link_hifi_libraries(shared gl gpu gpu-gl-common shaders) if (UNIX) target_link_libraries(${TARGET_NAME} pthread) endif(UNIX) diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h index 5d691d032a..881487c9db 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h +++ b/libraries/gpu-gl/src/gpu/gl41/GL41Backend.h @@ -170,8 +170,7 @@ protected: // Output stage void do_blit(const Batch& batch, size_t paramOffset) override; - std::string getBackendShaderHeader() const override; - + shader::Dialect getShaderDialect() const override { return shader::Dialect::glsl410; } void postLinkProgram(ShaderObject& programObject, const Shader& program) const override; }; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp index 46f91fdc15..f162afc497 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendShader.cpp @@ -12,22 +12,13 @@ using namespace gpu; using namespace gpu::gl; using namespace gpu::gl41; -// GLSL version -std::string GL41Backend::getBackendShaderHeader() const { - static const std::string header( - R"SHADER(#version 410 core - #define GPU_GL410 - #define BITFIELD int - )SHADER"); - return header; -} - void GL41Backend::postLinkProgram(ShaderObject& programObject, const Shader& program) const { Parent::postLinkProgram(programObject, program); const auto& glprogram = programObject.glprogram; + const auto& reflection = program.getReflection(); // For the UBOs, use glUniformBlockBinding to fixup the locations based on the reflection { - const auto expectedUbos = program.getUniformBuffers().getLocationsByName(); + const auto& expectedUbos = reflection.uniformBuffers; auto ubos = ::gl::UniformBlock::load(glprogram); for (const auto& ubo : ubos) { const auto& name = ubo.name; @@ -41,7 +32,7 @@ void GL41Backend::postLinkProgram(ShaderObject& programObject, const Shader& pro // For the Textures, use glUniform1i to fixup the active texture slots based on the reflection { - const auto expectedTextures = program.getTextures().getLocationsByName(); + const auto& expectedTextures = reflection.textures; for (const auto& expectedTexture : expectedTextures) { auto location = glGetUniformLocation(glprogram, expectedTexture.first.c_str()); if (location < 0) { @@ -53,8 +44,9 @@ void GL41Backend::postLinkProgram(ShaderObject& programObject, const Shader& pro // For the resource buffers, do the same as for the textures, since in GL 4.1 that's how they're implemented { - const auto expectedResourceBuffers = program.getResourceBuffers().getLocationsByName(); - const auto resourceBufferUniforms = ::gl::Uniform::loadByName(glprogram, program.getResourceBuffers().getNames()); + const auto& expectedResourceBuffers = reflection.resourceBuffers; + const auto names = Shader::Reflection::getNames(expectedResourceBuffers); + const auto resourceBufferUniforms = ::gl::Uniform::loadByName(glprogram, names); for (const auto& resourceBuffer : resourceBufferUniforms) { const auto& targetBinding = expectedResourceBuffers.at(resourceBuffer.name); glProgramUniform1i(glprogram, resourceBuffer.binding, targetBinding + GL41Backend::RESOURCE_BUFFER_SLOT0_TEX_UNIT); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h index 77095375af..c1ce074188 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h +++ b/libraries/gpu-gl/src/gpu/gl45/GL45Backend.h @@ -23,7 +23,7 @@ #define GPU_BINDLESS_TEXTURES 0 namespace gpu { namespace gl45 { - + using namespace gpu::gl; using TextureWeakPointer = std::weak_ptr; @@ -56,6 +56,7 @@ public: using Parent = GLTexture; friend class GL45Backend; static GLuint allocate(const Texture& texture); + protected: GL45Texture(const std::weak_ptr& backend, const Texture& texture); void generateMips() const override; @@ -88,6 +89,7 @@ public: virtual const Bindless& getBindless() const; void releaseBindless() const; void recreateBindless() const; + private: mutable Bindless _bindless; #endif @@ -98,10 +100,11 @@ public: mutable Sampler _cachedSampler{ getInvalidSampler() }; }; -#if GPU_BINDLESS_TEXTURES +#if GPU_BINDLESS_TEXTURES class GL45TextureTable : public GLObject { static GLuint allocate(); using Parent = GLObject; + public: using BindlessArray = std::array; @@ -116,7 +119,6 @@ public: }; #endif - // // Textures that have fixed allocation sizes and cannot be managed at runtime // @@ -134,12 +136,13 @@ public: void allocateStorage() const; void syncSampler() const override; - const Size _size { 0 }; + const Size _size{ 0 }; }; class GL45AttachmentTexture : public GL45FixedAllocationTexture { using Parent = GL45FixedAllocationTexture; friend class GL45Backend; + protected: GL45AttachmentTexture(const std::weak_ptr& backend, const Texture& texture); ~GL45AttachmentTexture(); @@ -148,6 +151,7 @@ public: class GL45StrictResourceTexture : public GL45FixedAllocationTexture { using Parent = GL45FixedAllocationTexture; friend class GL45Backend; + protected: GL45StrictResourceTexture(const std::weak_ptr& backend, const Texture& texture); ~GL45StrictResourceTexture(); @@ -179,6 +183,7 @@ public: class GL45ResourceTexture : public GL45VariableAllocationTexture { using Parent = GL45VariableAllocationTexture; friend class GL45Backend; + protected: GL45ResourceTexture(const std::weak_ptr& backend, const Texture& texture); @@ -186,7 +191,6 @@ public: size_t promote() override; size_t demote() override; void populateTransferQueue(TransferQueue& pendingTransfers) override; - void allocateStorage(uint16 mip); Size copyMipsFromTexture(); @@ -226,7 +230,6 @@ public: }; #endif - protected: void draw(GLenum mode, uint32 numVertices, uint32 startVertex) override; @@ -244,7 +247,6 @@ protected: GLuint getQueryID(const QueryPointer& query) override; GLQuery* syncGPUObject(const Query& query) override; - // Draw Stage void do_draw(const Batch& batch, size_t paramOffset) override; void do_drawIndexed(const Batch& batch, size_t paramOffset) override; @@ -270,7 +272,7 @@ protected: void do_blit(const Batch& batch, size_t paramOffset) override; // Shader Stage - std::string getBackendShaderHeader() const override; + shader::Dialect getShaderDialect() const override; // Texture Management Stage void initTextureManagementStage() override; @@ -282,9 +284,8 @@ protected: #endif }; -} } +}} // namespace gpu::gl45 Q_DECLARE_LOGGING_CATEGORY(gpugl45logging) #endif - diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp index 6cc0d226d6..cf8279b8e6 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp @@ -7,22 +7,16 @@ // #include "GL45Backend.h" #include -//#include using namespace gpu; using namespace gpu::gl; using namespace gpu::gl45; -// GLSL version -std::string GL45Backend::getBackendShaderHeader() const { - static const std::string header( - R"SHADER(#version 450 core - #define GPU_GL450 - #define BITFIELD int - )SHADER" -#ifdef GPU_SSBO_TRANSFORM_OBJECT - R"SHADER(#define GPU_SSBO_TRANSFORM_OBJECT)SHADER" +shader::Dialect GL45Backend::getShaderDialect() const { +#if defined(Q_OS_MAC) + // We build, but don't actually use GL 4.5 on OSX + throw std::runtime_error("GL 4.5 unavailable on OSX"); +#else + return shader::Dialect::glsl450; #endif - ); - return header; } diff --git a/libraries/gpu-gles/CMakeLists.txt b/libraries/gpu-gles/CMakeLists.txt index 82bf670781..3e529f7dcd 100644 --- a/libraries/gpu-gles/CMakeLists.txt +++ b/libraries/gpu-gles/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME gpu-gles) setup_hifi_library(Gui Concurrent) -link_hifi_libraries(shared gl gpu gpu-gl-common) +link_hifi_libraries(shared shaders gl gpu gpu-gl-common) GroupSources("src") target_opengl() diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h index 7f6765c129..8ecdb2494b 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.h +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.h @@ -27,11 +27,7 @@ class GLESBackend : public GLBackend { friend class Context; public: - static const GLint TRANSFORM_OBJECT_SLOT { 31 }; static const GLint RESOURCE_TRANSFER_TEX_UNIT { 32 }; - static const GLint RESOURCE_TRANSFER_EXTRA_TEX_UNIT { 33 }; - static const GLint RESOURCE_BUFFER_TEXBUF_TEX_UNIT { 34 }; - static const GLint RESOURCE_BUFFER_SLOT0_TEX_UNIT { 35 }; explicit GLESBackend(bool syncCache) : Parent(syncCache) {} GLESBackend() : Parent() {} virtual ~GLESBackend() { @@ -166,7 +162,7 @@ protected: // Output stage void do_blit(const Batch& batch, size_t paramOffset) override; - std::string getBackendShaderHeader() const override; + shader::Dialect getShaderDialect() const override { return shader::Dialect::glsl310es; } }; } } diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp index ee8408c533..dded307249 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendShader.cpp @@ -12,15 +12,3 @@ using namespace gpu; using namespace gpu::gl; using namespace gpu::gles; -// GLSL version -std::string GLESBackend::getBackendShaderHeader() const { - static const std::string header( - R"SHADER(#version 310 es - #extension GL_EXT_texture_buffer : enable - precision highp float; - precision highp samplerBuffer; - precision highp sampler2DShadow; - #define BITFIELD highp int - )SHADER"); - return header; -} diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp index 661eb0de99..7e1ee0da3b 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTransform.cpp @@ -60,12 +60,11 @@ void GLESBackend::transferTransformState(const Batch& batch) const { glBindBuffer(GL_ARRAY_BUFFER, 0); } - glActiveTexture(GL_TEXTURE0 + GLESBackend::TRANSFORM_OBJECT_SLOT); + glActiveTexture(GL_TEXTURE0 + slot::texture::ObjectTransforms); glBindTexture(GL_TEXTURE_BUFFER, _transform._objectBufferTexture); if (!batch._objects.empty()) { glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _transform._objectBuffer); } - CHECK_GL_ERROR(); // Make sure the current Camera offset is unknown before render Draw diff --git a/libraries/gpu/src/gpu/DrawColor.slf b/libraries/gpu/src/gpu/DrawColor.slf index fdea26fa68..3d5b569662 100644 --- a/libraries/gpu/src/gpu/DrawColor.slf +++ b/libraries/gpu/src/gpu/DrawColor.slf @@ -17,7 +17,7 @@ struct DrawColorParams { vec4 color; }; -layout(binding=0) uniform drawColorParamsBuffer { +LAYOUT(binding=0) uniform drawColorParamsBuffer { DrawColorParams params; }; diff --git a/libraries/gpu/src/gpu/DrawColoredTexture.slf b/libraries/gpu/src/gpu/DrawColoredTexture.slf index 0fe3707b1c..a4f03f925d 100755 --- a/libraries/gpu/src/gpu/DrawColoredTexture.slf +++ b/libraries/gpu/src/gpu/DrawColoredTexture.slf @@ -13,13 +13,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -layout(binding=0) uniform sampler2D colorMap; +LAYOUT(binding=0) uniform sampler2D colorMap; struct DrawColorParams { vec4 color; }; -layout(binding=0) uniform drawColorParams { +LAYOUT(binding=0) uniform drawColorParams { DrawColorParams params; }; diff --git a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv index 8849cb494a..a59180ec31 100755 --- a/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv +++ b/libraries/gpu/src/gpu/DrawTexcoordRectTransformUnitQuad.slv @@ -25,7 +25,7 @@ struct TexCoordRectParams { vec4 texcoordRect; }; -layout(binding=0) uniform texcoordRectBuffer { +LAYOUT(binding=0) uniform texcoordRectBuffer { TexCoordRectParams params; }; diff --git a/libraries/gpu/src/gpu/DrawTexture.slf b/libraries/gpu/src/gpu/DrawTexture.slf index 4298729b8b..f8f06eb6ca 100755 --- a/libraries/gpu/src/gpu/DrawTexture.slf +++ b/libraries/gpu/src/gpu/DrawTexture.slf @@ -14,7 +14,7 @@ // -layout(binding=0) uniform sampler2D colorMap; +LAYOUT(binding=0) uniform sampler2D colorMap; layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; diff --git a/libraries/gpu/src/gpu/drawTexture.slp b/libraries/gpu/src/gpu/DrawTexture.slp similarity index 57% rename from libraries/gpu/src/gpu/drawTexture.slp rename to libraries/gpu/src/gpu/DrawTexture.slp index e04be84618..f922364b75 100644 --- a/libraries/gpu/src/gpu/drawTexture.slp +++ b/libraries/gpu/src/gpu/DrawTexture.slp @@ -1,2 +1 @@ VERTEX DrawUnitQuadTexcoord -FRAGMENT DrawTexture diff --git a/libraries/gpu/src/gpu/DrawTextureMirroredX.slf b/libraries/gpu/src/gpu/DrawTextureMirroredX.slf index ab6333f08d..abb52cbe7f 100644 --- a/libraries/gpu/src/gpu/DrawTextureMirroredX.slf +++ b/libraries/gpu/src/gpu/DrawTextureMirroredX.slf @@ -14,7 +14,7 @@ // -layout(binding=0) uniform sampler2D colorMap; +LAYOUT(binding=0) uniform sampler2D colorMap; layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; diff --git a/libraries/gpu/src/gpu/DrawTextureMirroredX.slp b/libraries/gpu/src/gpu/DrawTextureMirroredX.slp new file mode 100644 index 0000000000..db9a4a4fac --- /dev/null +++ b/libraries/gpu/src/gpu/DrawTextureMirroredX.slp @@ -0,0 +1 @@ +VERTEX DrawUnitQuadTexcoord \ No newline at end of file diff --git a/libraries/gpu/src/gpu/DrawTextureOpaque.slf b/libraries/gpu/src/gpu/DrawTextureOpaque.slf index b3227325bf..e23529e851 100755 --- a/libraries/gpu/src/gpu/DrawTextureOpaque.slf +++ b/libraries/gpu/src/gpu/DrawTextureOpaque.slf @@ -16,7 +16,7 @@ <@include gpu/ShaderConstants.h@> -layout(binding=0) uniform sampler2D colorMap; +LAYOUT(binding=0) uniform sampler2D colorMap; layout(location=0) in vec2 varTexCoord0; diff --git a/libraries/gpu/src/gpu/DrawTransformedTexture.slp b/libraries/gpu/src/gpu/DrawTransformedTexture.slp new file mode 100644 index 0000000000..daeafe6012 --- /dev/null +++ b/libraries/gpu/src/gpu/DrawTransformedTexture.slp @@ -0,0 +1,2 @@ +VERTEX DrawTransformUnitQuad +FRAGMENT DrawTexture diff --git a/libraries/gpu/src/gpu/Shader.cpp b/libraries/gpu/src/gpu/Shader.cpp index 0191d9d4f1..d4236ac66c 100755 --- a/libraries/gpu/src/gpu/Shader.cpp +++ b/libraries/gpu/src/gpu/Shader.cpp @@ -10,44 +10,49 @@ // #include "Shader.h" -#include -#include -#include - -#include -#include - -#include #include "Context.h" using namespace gpu; -std::atomic Shader::_nextShaderID( 1 ); -Shader::DomainShaderMaps Shader::_domainShaderMaps; Shader::ProgramMap Shader::_programMap; -Shader::Shader(Type type, const Source& source) : - _source(source), - _type(type), - _ID(_nextShaderID++) +Shader::Shader(Type type, const Source& source, bool dynamic) : + _type(type) { + auto& thisSource = const_cast(_source); + thisSource = source; + if (!dynamic) { + thisSource.id = source.id; + } } -Shader::Shader(Type type, const Pointer& vertex, const Pointer& geometry, const Pointer& pixel): - _type(type), - _ID(_nextShaderID++) +Shader::Shader(Type type, const Pointer& vertex, const Pointer& geometry, const Pointer& pixel) : + _type(type) { + + auto& shaders = const_cast(_shaders); if (geometry) { - _shaders.resize(3); - _shaders[VERTEX] = vertex; - _shaders[GEOMETRY] = geometry; - _shaders[PIXEL] = pixel; + shaders.resize(3); + shaders[VERTEX] = vertex; + shaders[GEOMETRY] = geometry; + shaders[PIXEL] = pixel; } else { - _shaders.resize(2); - _shaders[VERTEX] = vertex; - _shaders[PIXEL] = pixel; + shaders.resize(2); + shaders[VERTEX] = vertex; + shaders[PIXEL] = pixel; + } + + auto& reflection = const_cast(getReflection()); + for (const auto& subShader : _shaders) { + reflection.merge(subShader->getReflection()); + } + if (_shaders[VERTEX]) { + reflection.inputs = _shaders[VERTEX]->getReflection().inputs; + } + if (_shaders[PIXEL]) { + reflection.outputs = _shaders[PIXEL]->getReflection().outputs; } } @@ -55,46 +60,27 @@ Shader::~Shader() { } -void populateSlotSet(Shader::SlotSet& slotSet, const Shader::LocationMap& map) { - for (const auto& entry : map) { - const auto& name = entry.first; - const auto& location = entry.second; - slotSet.insert({ name, location, Element() }); - } +static std::unordered_map> _shaderCache; + +Shader::ID Shader::getID() const { + if (isProgram()) { + return (_shaders[VERTEX]->getID() << 16) | (_shaders[PIXEL]->getID()); + } + + return _source.id; } -Shader::Pointer Shader::createOrReuseDomainShader(Type type, const Source& source) { - auto found = _domainShaderMaps[type].find(source); - if (found != _domainShaderMaps[type].end()) { +Shader::Pointer Shader::createOrReuseDomainShader(Type type, uint32_t sourceId) { + // Don't attempt to cache non-static shaders + auto found = _shaderCache.find(sourceId); + if (found != _shaderCache.end()) { auto sharedShader = (*found).second.lock(); if (sharedShader) { return sharedShader; } } - auto shader = Pointer(new Shader(type, source)); - const auto& reflection = source.getReflection(); - if (0 != reflection.count(BindingType::INPUT)) { - populateSlotSet(shader->_inputs, reflection.find(BindingType::INPUT)->second); - } - if (0 != reflection.count(BindingType::OUTPUT)) { - populateSlotSet(shader->_outputs, reflection.find(BindingType::OUTPUT)->second); - } - if (0 != reflection.count(BindingType::UNIFORM_BUFFER)) { - populateSlotSet(shader->_uniformBuffers, reflection.find(BindingType::UNIFORM_BUFFER)->second); - } - if (0 != reflection.count(BindingType::RESOURCE_BUFFER)) { - populateSlotSet(shader->_resourceBuffers, reflection.find(BindingType::RESOURCE_BUFFER)->second); - } - if (0 != reflection.count(BindingType::TEXTURE)) { - populateSlotSet(shader->_textures, reflection.find(BindingType::TEXTURE)->second); - } - if (0 != reflection.count(BindingType::SAMPLER)) { - populateSlotSet(shader->_samplers, reflection.find(BindingType::SAMPLER)->second); - } - if (0 != reflection.count(BindingType::UNIFORM)) { - populateSlotSet(shader->_uniforms, reflection.find(BindingType::UNIFORM)->second); - } - _domainShaderMaps[type].emplace(source, std::weak_ptr(shader)); + auto shader = Pointer(new Shader(type, getShaderSource(sourceId), false)); + _shaderCache.insert({ sourceId, shader }); return shader; } @@ -137,28 +123,6 @@ ShaderPointer Shader::createOrReuseProgramShader(Type type, const Pointer& verte // Program is a new one, let's create it auto program = Pointer(new Shader(type, vertexShader, geometryShader, pixelShader)); - - // Combine the slots from the sub-shaders - for (const auto& shader : program->_shaders) { - const auto& reflection = shader->_source.getReflection(); - if (0 != reflection.count(BindingType::UNIFORM_BUFFER)) { - populateSlotSet(program->_uniformBuffers, reflection.find(BindingType::UNIFORM_BUFFER)->second); - } - if (0 != reflection.count(BindingType::RESOURCE_BUFFER)) { - populateSlotSet(program->_resourceBuffers, reflection.find(BindingType::RESOURCE_BUFFER)->second); - } - if (0 != reflection.count(BindingType::TEXTURE)) { - populateSlotSet(program->_textures, reflection.find(BindingType::TEXTURE)->second); - } - if (0 != reflection.count(BindingType::SAMPLER)) { - populateSlotSet(program->_samplers, reflection.find(BindingType::SAMPLER)->second); - } - if (0 != reflection.count(BindingType::UNIFORM)) { - populateSlotSet(program->_uniforms, reflection.find(BindingType::UNIFORM)->second); - } - - } - _programMap.emplace(key, std::weak_ptr(program)); return program; } @@ -175,24 +139,21 @@ void Shader::incrementCompilationAttempt() const { } Shader::Pointer Shader::createVertex(const Source& source) { - return createOrReuseDomainShader(VERTEX, source); + return Pointer(new Shader(VERTEX, source, true)); } Shader::Pointer Shader::createPixel(const Source& source) { - return createOrReuseDomainShader(FRAGMENT, source); + return Pointer(new Shader(FRAGMENT, source, true)); } Shader::Pointer Shader::createVertex(uint32_t id) { - return createVertex(getShaderSource(id)); + return createOrReuseDomainShader(VERTEX, id); } Shader::Pointer Shader::createPixel(uint32_t id) { - return createPixel(getShaderSource(id)); + return createOrReuseDomainShader(FRAGMENT, id); } -Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer& pixelShader) { - return createOrReuseProgramShader(PROGRAM, vertexShader, nullptr, pixelShader); -} Shader::Pointer Shader::createProgram(uint32_t programId) { auto vertexShader = createVertex(shader::getVertexId(programId)); @@ -200,98 +161,15 @@ Shader::Pointer Shader::createProgram(uint32_t programId) { return createOrReuseProgramShader(PROGRAM, vertexShader, nullptr, fragmentShader); } +Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer& pixelShader) { + return Pointer(new Shader(PROGRAM, vertexShader, nullptr, pixelShader)); +} + +// Dynamic program, bypass caching Shader::Pointer Shader::createProgram(const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader) { - return createOrReuseProgramShader(PROGRAM, vertexShader, geometryShader, pixelShader); + return Pointer(new Shader(PROGRAM, vertexShader, geometryShader, pixelShader)); } -static const std::string IGNORED_BINDING = "transformObjectBuffer"; - -void updateBindingsFromJsonObject(Shader::LocationMap& inOutSet, const QJsonObject& json) { - for (const auto& key : json.keys()) { - auto keyStr = key.toStdString(); - if (IGNORED_BINDING == keyStr) { - continue; - } - inOutSet[keyStr] = json[key].toInt(); - } -} - -void updateTextureAndResourceBuffersFromJsonObjects(Shader::LocationMap& inOutTextures, Shader::LocationMap& inOutResourceBuffers, - const QJsonObject& json, const QJsonObject& types) { - static const std::string RESOURCE_BUFFER_TEXTURE_TYPE = "samplerBuffer"; - for (const auto& key : json.keys()) { - auto keyStr = key.toStdString(); - if (keyStr == IGNORED_BINDING) { - continue; - } - auto location = json[key].toInt(); - auto type = types[key].toString().toStdString(); - if (type == RESOURCE_BUFFER_TEXTURE_TYPE) { - inOutResourceBuffers[keyStr] = location; - } else { - inOutTextures[key.toStdString()] = location; - } - } -} - -Shader::ReflectionMap getShaderReflection(const std::string& reflectionJson) { - if (reflectionJson.empty() && reflectionJson != std::string("null")) { - return {}; - } - -#define REFLECT_KEY_INPUTS "inputs" -#define REFLECT_KEY_OUTPUTS "outputs" -#define REFLECT_KEY_UBOS "uniformBuffers" -#define REFLECT_KEY_SSBOS "storageBuffers" -#define REFLECT_KEY_UNIFORMS "uniforms" -#define REFLECT_KEY_TEXTURES "textures" -#define REFLECT_KEY_TEXTURE_TYPES "textureTypes" - - auto doc = QJsonDocument::fromJson(reflectionJson.c_str()); - if (doc.isNull()) { - qWarning() << "Invalid shader reflection JSON" << reflectionJson.c_str(); - return {}; - } - - Shader::ReflectionMap result; - auto json = doc.object(); - if (json.contains(REFLECT_KEY_INPUTS)) { - updateBindingsFromJsonObject(result[Shader::BindingType::INPUT], json[REFLECT_KEY_INPUTS].toObject()); - } - if (json.contains(REFLECT_KEY_OUTPUTS)) { - updateBindingsFromJsonObject(result[Shader::BindingType::OUTPUT], json[REFLECT_KEY_OUTPUTS].toObject()); - } - // FIXME eliminate the last of the uniforms - if (json.contains(REFLECT_KEY_UNIFORMS)) { - updateBindingsFromJsonObject(result[Shader::BindingType::UNIFORM], json[REFLECT_KEY_UNIFORMS].toObject()); - } - if (json.contains(REFLECT_KEY_UBOS)) { - updateBindingsFromJsonObject(result[Shader::BindingType::UNIFORM_BUFFER], json[REFLECT_KEY_UBOS].toObject()); - } - - // SSBOs need to come BEFORE the textures. In GL 4.5 the reflection slots aren't really used, but in 4.1 the slots - // are used to explicitly setup bindings after shader linkage, so we want the resource buffer slots to contain the - // texture locations, not the SSBO locations - if (json.contains(REFLECT_KEY_SSBOS)) { - updateBindingsFromJsonObject(result[Shader::BindingType::RESOURCE_BUFFER], json[REFLECT_KEY_SSBOS].toObject()); - } - - // samplerBuffer textures map to gpu ResourceBuffer, while all other textures map to regular gpu Texture - if (json.contains(REFLECT_KEY_TEXTURES)) { - updateTextureAndResourceBuffersFromJsonObjects( - result[Shader::BindingType::TEXTURE], - result[Shader::BindingType::RESOURCE_BUFFER], - json[REFLECT_KEY_TEXTURES].toObject(), - json[REFLECT_KEY_TEXTURE_TYPES].toObject()); - } - - - return result; -} - -Shader::Source Shader::getShaderSource(uint32_t id) { - auto source = shader::loadShaderSource(id); - auto reflectionJson = shader::loadShaderReflection(id); - auto reflection = getShaderReflection(reflectionJson); - return { source, reflection }; +const Shader::Source& Shader::getShaderSource(uint32_t id) { + return shader::Source::get(id); } diff --git a/libraries/gpu/src/gpu/Shader.h b/libraries/gpu/src/gpu/Shader.h index ad828a0cff..35426bed20 100755 --- a/libraries/gpu/src/gpu/Shader.h +++ b/libraries/gpu/src/gpu/Shader.h @@ -19,6 +19,7 @@ #include #include #include +#include #include namespace gpu { @@ -42,58 +43,10 @@ public: typedef std::shared_ptr Pointer; typedef std::vector Shaders; - // Needs to match values in shaders/Shaders.h - enum class BindingType - { - INVALID = -1, - INPUT = 0, - OUTPUT, - TEXTURE, - SAMPLER, - UNIFORM_BUFFER, - RESOURCE_BUFFER, - UNIFORM, - }; - - using LocationMap = std::unordered_map; - using ReflectionMap = std::map; - - class Source { - public: - enum Language - { - INVALID = -1, - GLSL = 0, - SPIRV = 1, - MSL = 2, - HLSL = 3, - }; - - Source() {} - Source(const std::string& code, const ReflectionMap& reflection, Language lang = GLSL) : - _code(code), _reflection(reflection), _lang(lang) {} - Source(const Source& source) : _code(source._code), _reflection(source._reflection), _lang(source._lang) {} - virtual ~Source() {} - - virtual const std::string& getCode() const { return _code; } - virtual const ReflectionMap& getReflection() const { return _reflection; } - - class Less { - public: - bool operator()(const Source& x, const Source& y) const { - if (x._lang == y._lang) { - return x._code < y._code; - } else { - return (x._lang < y._lang); - } - } - }; - - protected: - std::string _code; - ReflectionMap _reflection; - Language _lang; - }; + using Source = shader::Source; + using Reflection = shader::Reflection; + using Dialect = shader::Dialect; + using Variant = shader::Variant; struct CompilationLog { std::string message; @@ -112,85 +65,13 @@ public: bool operator()(const T& x, const T& y) const { return x._name < y._name; } }; - class Slot { - public: - std::string _name; - int32 _location{ INVALID_LOCATION }; - Element _element; - uint16 _resourceType{ Resource::BUFFER }; - uint32 _size{ 0 }; - - Slot(const Slot& s) : - _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType), _size(s._size) {} - Slot(Slot&& s) : - _name(s._name), _location(s._location), _element(s._element), _resourceType(s._resourceType), _size(s._size) {} - Slot(const std::string& name, - int32 location, - const Element& element, - uint16 resourceType = Resource::BUFFER, - uint32 size = 0) : - _name(name), - _location(location), _element(element), _resourceType(resourceType), _size(size) {} - Slot(const std::string& name) : _name(name) {} - - Slot& operator=(const Slot& s) { - _name = s._name; - _location = s._location; - _element = s._element; - _resourceType = s._resourceType; - _size = s._size; - return (*this); - } - }; - - class SlotSet : protected std::set> { - using Parent = std::set>; - - public: - void insert(const Parent::value_type& value) { - Parent::insert(value); - if (value._location != INVALID_LOCATION) { - _validSlots.insert(value._location); - } - } - - using Parent::begin; - using Parent::empty; - using Parent::end; - using Parent::size; - - using LocationMap = std::unordered_map; - using NameVector = std::vector; - - NameVector getNames() const { - NameVector result; - for (const auto& entry : *this) { - result.push_back(entry._name); - } - return result; - } - - LocationMap getLocationsByName() const { - LocationMap result; - for (const auto& entry : *this) { - result.insert({ entry._name, entry._location }); - } - return result; - } - - bool isValid(int32 slot) const { return 0 != _validSlots.count(slot); } - - protected: - std::unordered_set _validSlots; - }; - - static Source getShaderSource(uint32_t id); - static Source getVertexShaderSource(uint32_t id) { return getShaderSource(id); } - static Source getFragmentShaderSource(uint32_t id) { return getShaderSource(id); } - + static const Source& getShaderSource(uint32_t id); + static const Source& getVertexShaderSource(uint32_t id) { return getShaderSource(id); } + static const Source& getFragmentShaderSource(uint32_t id) { return getShaderSource(id); } static Pointer createVertex(const Source& source); static Pointer createPixel(const Source& source); static Pointer createGeometry(const Source& source); + static Pointer createVertex(uint32_t shaderId); static Pointer createPixel(uint32_t shaderId); static Pointer createGeometry(uint32_t shaderId); @@ -201,8 +82,7 @@ public: ~Shader(); - ID getID() const { return _ID; } - + ID getID() const; Type getType() const { return _type; } bool isProgram() const { return getType() > NUM_DOMAINS; } bool isDomain() const { return getType() < NUM_DOMAINS; } @@ -211,17 +91,12 @@ public: const Shaders& getShaders() const { return _shaders; } - // Access the exposed uniform, input and output slot - const SlotSet& getUniforms() const { return _uniforms; } - const SlotSet& getUniformBuffers() const { return _uniformBuffers; } - const SlotSet& getResourceBuffers() const { return _resourceBuffers; } - const SlotSet& getTextures() const { return _textures; } - const SlotSet& getSamplers() const { return _samplers; } + const Reflection& getReflection() const { return _source.reflection; } // Compilation Handler can be passed while compiling a shader (in the makeProgram call) to be able to give the hand to - // the caller thread if the comilation fails and to prvide a different version of the source for it + // the caller thread if the compilation fails and to provide a different version of the source for it // @param0 the Shader object that just failed to compile - // @param1 the original source code as submited to the compiler + // @param1 the original source code as submitted to the compiler // @param2 the compilation log containing the error message // @param3 a new string ready to be filled with the new version of the source that could be proposed from the handler functor // @return boolean true if the backend should keep trying to compile the shader with the new source returned or false to stop and fail that shader compilation @@ -240,32 +115,21 @@ public: const GPUObjectPointer gpuObject{}; protected: - Shader(Type type, const Source& source); + Shader(Type type, const Source& source, bool dynamic); Shader(Type type, const Pointer& vertex, const Pointer& geometry, const Pointer& pixel); Shader(const Shader& shader); // deep copy of the sysmem shader Shader& operator=(const Shader& shader); // deep copy of the sysmem texture // Source contains the actual source code or nothing if the shader is a program - Source _source; + const Source _source; // if shader is composed of sub shaders, here they are - Shaders _shaders; - - // List of exposed uniform, input and output slots - SlotSet _uniforms; - SlotSet _uniformBuffers; - SlotSet _resourceBuffers; - SlotSet _textures; - SlotSet _samplers; - SlotSet _inputs; - SlotSet _outputs; + const Shaders _shaders; + // The type of the shader, the master key - Type _type; - - // The unique identifier of a shader in the GPU lib - uint32_t _ID{ 0 }; + const Type _type; // Number of attempts to compile the shader mutable uint32_t _numCompilationAttempts{ 0 }; @@ -277,13 +141,9 @@ protected: // Global maps of the shaders // Unique shader ID - static std::atomic _nextShaderID; + //static std::atomic _nextShaderID; - using ShaderMap = std::map, Source::Less>; - using DomainShaderMaps = std::array; - static DomainShaderMaps _domainShaderMaps; - - static ShaderPointer createOrReuseDomainShader(Type type, const Source& source); + static ShaderPointer createOrReuseDomainShader(Type type, uint32_t sourceId); using ProgramMapKey = glm::uvec3; // The IDs of the shaders in a program make its key class ProgramKeyLess { diff --git a/libraries/gpu/src/gpu/ShaderConstants.h b/libraries/gpu/src/gpu/ShaderConstants.h index 0724b4eb40..e9a1821ef4 100644 --- a/libraries/gpu/src/gpu/ShaderConstants.h +++ b/libraries/gpu/src/gpu/ShaderConstants.h @@ -98,21 +98,6 @@ enum Attribute { }; } // namespace attr -namespace uniform { -enum Uniform { - Extra0 = GPU_UNIFORM_EXTRA0, - Extra1 = GPU_UNIFORM_EXTRA1, - Extra2 = GPU_UNIFORM_EXTRA2, - Extra3 = GPU_UNIFORM_EXTRA3, - Extra4 = GPU_UNIFORM_EXTRA4, - Extra5 = GPU_UNIFORM_EXTRA5, - Extra6 = GPU_UNIFORM_EXTRA6, - Extra7 = GPU_UNIFORM_EXTRA7, - Extra8 = GPU_UNIFORM_EXTRA8, - Extra9 = GPU_UNIFORM_EXTRA9, -}; -} // namespace uniform - } } // namespace gpu::slot // !> diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index fd2cb86177..43205ba4c2 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -16,7 +16,7 @@ #define TransformCamera _TransformCamera -layout(std140, binding=GPU_BUFFER_TRANSFORM_CAMERA) uniform transformCameraBuffer { +LAYOUT_STD140(binding=GPU_BUFFER_TRANSFORM_CAMERA) uniform transformCameraBuffer { #ifdef GPU_TRANSFORM_IS_STEREO #ifdef GPU_TRANSFORM_STEREO_CAMERA TransformCamera _camera[2]; @@ -26,7 +26,7 @@ layout(std140, binding=GPU_BUFFER_TRANSFORM_CAMERA) uniform transformCameraBuffe #else TransformCamera _camera; #endif -}; +} _cameraBlock; #ifdef GPU_VERTEX_SHADER #ifdef GPU_TRANSFORM_IS_STEREO @@ -76,12 +76,12 @@ TransformCamera getTransformCamera() { _stereoSide = gl_InstanceID % 2; #endif #endif - return _camera[_stereoSide]; + return _cameraBlock._camera[_stereoSide]; #else - return _camera; + return _cameraBlock._camera; #endif #else - return _camera; + return _cameraBlock._camera; #endif } @@ -93,7 +93,7 @@ bool cam_isStereo() { #ifdef GPU_TRANSFORM_IS_STEREO return getTransformCamera()._stereoInfo.x > 0.0; #else - return _camera._stereoInfo.x > 0.0; + return _cameraBlock._camera._stereoInfo.x > 0.0; #endif } @@ -102,10 +102,10 @@ float cam_getStereoSide() { #ifdef GPU_TRANSFORM_STEREO_CAMERA return getTransformCamera()._stereoInfo.y; #else - return _camera._stereoInfo.y; + return _cameraBlock._camera._stereoInfo.y; #endif #else - return _camera._stereoInfo.y; + return _cameraBlock._camera._stereoInfo.y; #endif } @@ -120,7 +120,7 @@ struct TransformObject { layout(location=GPU_ATTR_DRAW_CALL_INFO) in ivec2 _drawCallInfo; #if defined(GPU_SSBO_TRANSFORM_OBJECT) -layout(std140, binding=GPU_STORAGE_TRANSFORM_OBJECT) buffer transformObjectBuffer { +LAYOUT_STD140(binding=GPU_STORAGE_TRANSFORM_OBJECT) buffer transformObjectBuffer { TransformObject _object[]; }; TransformObject getTransformObject() { @@ -128,7 +128,7 @@ TransformObject getTransformObject() { return transformObject; } #else -layout(binding=GPU_TEXTURE_TRANSFORM_OBJECT) uniform samplerBuffer transformObjectBuffer; +LAYOUT(binding=GPU_TEXTURE_TRANSFORM_OBJECT) uniform samplerBuffer transformObjectBuffer; TransformObject getTransformObject() { int offset = 8 * _drawCallInfo.x; @@ -167,7 +167,9 @@ TransformObject getTransformObject() { vec4 eyeClipEdge[2]= vec4[2](vec4(-1,0,0,1), vec4(1,0,0,1)); vec2 eyeOffsetScale = vec2(-0.5, +0.5); uint eyeIndex = uint(_stereoSide); +#ifndef GPU_GLES gl_ClipDistance[0] = dot(<$clipPos$>, eyeClipEdge[eyeIndex]); +#endif float newClipPosX = <$clipPos$>.x * 0.5 + eyeOffsetScale[eyeIndex] * <$clipPos$>.w; <$clipPos$>.x = newClipPosX; #endif diff --git a/libraries/graphics/src/graphics/Light.slh b/libraries/graphics/src/graphics/Light.slh index d22da44c66..c00bfea6a2 100644 --- a/libraries/graphics/src/graphics/Light.slh +++ b/libraries/graphics/src/graphics/Light.slh @@ -51,7 +51,7 @@ float getLightAmbientMapNumMips(LightAmbient l) { return l._ambient.y; } <@if N@> -layout(binding=GRAPHICS_BUFFER_LIGHT) uniform lightBuffer { +LAYOUT(binding=GRAPHICS_BUFFER_LIGHT) uniform lightBuffer { Light lightArray[<$N$>]; }; Light getLight(int index) { @@ -59,7 +59,7 @@ Light getLight(int index) { } <@else@> -layout(binding=GRAPHICS_BUFFER_KEY_LIGHT) uniform keyLightBuffer { +LAYOUT(binding=GRAPHICS_BUFFER_KEY_LIGHT) uniform keyLightBuffer { Light light; }; Light getKeyLight() { @@ -79,7 +79,7 @@ Light getKeyLight() { <@if N@> -layout(binding=GRAPHICS_BUFFER_AMBIENT_LIGHT) uniform lightAmbientBuffer { +LAYOUT(binding=GRAPHICS_BUFFER_AMBIENT_LIGHT) uniform lightAmbientBuffer { LightAmbient lightAmbientArray[<$N$>]; }; @@ -88,7 +88,7 @@ LightAmbient getLightAmbient(int index) { } <@else@> -layout(binding=GRAPHICS_BUFFER_AMBIENT_LIGHT) uniform lightAmbientBuffer { +LAYOUT(binding=GRAPHICS_BUFFER_AMBIENT_LIGHT) uniform lightAmbientBuffer { LightAmbient lightAmbient; }; diff --git a/libraries/graphics/src/graphics/Material.slh b/libraries/graphics/src/graphics/Material.slh index ea59059cf1..d2055b9a59 100644 --- a/libraries/graphics/src/graphics/Material.slh +++ b/libraries/graphics/src/graphics/Material.slh @@ -48,7 +48,7 @@ struct Material { vec4 _scatteringSpare2Key; }; -layout(binding=GRAPHICS_BUFFER_MATERIAL) uniform materialBuffer { +LAYOUT(binding=GRAPHICS_BUFFER_MATERIAL) uniform materialBuffer { Material _mat; TexMapArray _texMapArray; }; diff --git a/libraries/graphics/src/graphics/MaterialTextures.slh b/libraries/graphics/src/graphics/MaterialTextures.slh index 0a60feccfc..db329c3852 100644 --- a/libraries/graphics/src/graphics/MaterialTextures.slh +++ b/libraries/graphics/src/graphics/MaterialTextures.slh @@ -11,6 +11,7 @@ <@if not MODEL_MATERIAL_TEXTURES_SLH@> <@def MODEL_MATERIAL_TEXTURES_SLH@> +<@include graphics/ShaderConstants.h@> <@include graphics/Material.slh@> <@func declareMaterialTextures(withAlbedo, withRoughness, withNormal, withMetallic, withEmissive, withOcclusion, withScattering)@> @@ -91,21 +92,21 @@ float fetchScatteringMap(vec2 uv) { #else <@if withAlbedo@> -layout(binding=GRAPHICS_TEXTURE_MATERIAL_ALBEDO) uniform sampler2D albedoMap; +LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_ALBEDO) uniform sampler2D albedoMap; vec4 fetchAlbedoMap(vec2 uv) { return texture(albedoMap, uv, TAA_TEXTURE_LOD_BIAS); } <@endif@> <@if withRoughness@> -layout(binding=GRAPHICS_TEXTURE_MATERIAL_ROUGHNESS) uniform sampler2D roughnessMap; +LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_ROUGHNESS) uniform sampler2D roughnessMap; float fetchRoughnessMap(vec2 uv) { return (texture(roughnessMap, uv, TAA_TEXTURE_LOD_BIAS).r); } <@endif@> <@if withNormal@> -layout(binding=GRAPHICS_TEXTURE_MATERIAL_NORMAL) uniform sampler2D normalMap; +LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_NORMAL) uniform sampler2D normalMap; vec3 fetchNormalMap(vec2 uv) { // unpack normal, swizzle to get into hifi tangent space with Y axis pointing out vec2 t = 2.0 * (texture(normalMap, uv, TAA_TEXTURE_LOD_BIAS).rg - vec2(0.5, 0.5)); @@ -115,28 +116,28 @@ vec3 fetchNormalMap(vec2 uv) { <@endif@> <@if withMetallic@> -layout(binding=GRAPHICS_TEXTURE_MATERIAL_METALLIC) uniform sampler2D metallicMap; +LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_METALLIC) uniform sampler2D metallicMap; float fetchMetallicMap(vec2 uv) { return (texture(metallicMap, uv, TAA_TEXTURE_LOD_BIAS).r); } <@endif@> <@if withEmissive@> -layout(binding=GRAPHICS_TEXTURE_MATERIAL_EMISSIVE_LIGHTMAP) uniform sampler2D emissiveMap; +LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_EMISSIVE_LIGHTMAP) uniform sampler2D emissiveMap; vec3 fetchEmissiveMap(vec2 uv) { return texture(emissiveMap, uv, TAA_TEXTURE_LOD_BIAS).rgb; } <@endif@> <@if withOcclusion@> -layout(binding=GRAPHICS_TEXTURE_MATERIAL_OCCLUSION) uniform sampler2D occlusionMap; +LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_OCCLUSION) uniform sampler2D occlusionMap; float fetchOcclusionMap(vec2 uv) { return texture(occlusionMap, uv).r; } <@endif@> <@if withScattering@> -layout(binding=GRAPHICS_TEXTURE_MATERIAL_SCATTERING) uniform sampler2D scatteringMap; +LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_SCATTERING) uniform sampler2D scatteringMap; float fetchScatteringMap(vec2 uv) { float scattering = texture(scatteringMap, uv, TAA_TEXTURE_LOD_BIAS).r; // boolean scattering for now return max(((scattering - 0.1) / 0.9), 0.0); @@ -185,7 +186,7 @@ float fetchScatteringMap(vec2 uv) { <$declareMaterialTexMapArrayBuffer()$> -layout(binding=GRAPHICS_TEXTURE_MATERIAL_EMISSIVE_LIGHTMAP) uniform sampler2D emissiveMap; +LAYOUT(binding=GRAPHICS_TEXTURE_MATERIAL_EMISSIVE_LIGHTMAP) uniform sampler2D emissiveMap; vec3 fetchLightmapMap(vec2 uv) { vec2 lightmapParams = getTexMapArray()._lightmapParams.xy; return (vec3(lightmapParams.x) + lightmapParams.y * texture(emissiveMap, uv).rgb); diff --git a/libraries/graphics/src/graphics/skybox.slf b/libraries/graphics/src/graphics/skybox.slf index 2b81a433f1..b24bf0f583 100755 --- a/libraries/graphics/src/graphics/skybox.slf +++ b/libraries/graphics/src/graphics/skybox.slf @@ -12,13 +12,13 @@ // <@include graphics/ShaderConstants.h@> -layout(binding=GRAPHICS_TEXTURE_SKYBOX) uniform samplerCube cubeMap; +LAYOUT(binding=GRAPHICS_TEXTURE_SKYBOX) uniform samplerCube cubeMap; struct Skybox { vec4 color; }; -layout(binding=GRAPHICS_BUFFER_SKYBOX_PARAMS) uniform skyboxBuffer { +LAYOUT(binding=GRAPHICS_BUFFER_SKYBOX_PARAMS) uniform skyboxBuffer { Skybox skybox; }; diff --git a/libraries/model-networking/CMakeLists.txt b/libraries/model-networking/CMakeLists.txt index 9a4bc780a6..12181651db 100644 --- a/libraries/model-networking/CMakeLists.txt +++ b/libraries/model-networking/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME model-networking) setup_hifi_library() -link_hifi_libraries(shared networking graphics fbx ktx image gl) +link_hifi_libraries(shared shaders networking graphics fbx ktx image gl) include_hifi_library_headers(gpu) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 79c0b31dff..426c9ff893 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -27,7 +27,7 @@ Q_LOGGING_CATEGORY(proceduralLog, "hifi.gpu.procedural") -// Userdata parsing constants +// User-data parsing constants static const QString PROCEDURAL_USER_DATA_KEY = "ProceduralEntity"; static const QString URL_KEY = "shaderUrl"; static const QString VERSION_KEY = "version"; @@ -39,11 +39,8 @@ static const std::string PROCEDURAL_BLOCK = "//PROCEDURAL_BLOCK"; static const std::string PROCEDURAL_VERSION = "//PROCEDURAL_VERSION"; bool operator==(const ProceduralData& a, const ProceduralData& b) { - return ( - (a.version == b.version) && - (a.shaderUrl == b.shaderUrl) && - (a.uniforms == b.uniforms) && - (a.channels == b.channels)); + return ((a.version == b.version) && (a.shaderUrl == b.shaderUrl) && (a.uniforms == b.uniforms) && + (a.channels == b.channels)); } QJsonValue ProceduralData::getProceduralData(const QString& proceduralJson) { @@ -109,6 +106,8 @@ Procedural::Procedural() { _transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); _transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + + _standardInputsBuffer = std::make_shared(sizeof(StandardInputs), nullptr); } void Procedural::setProceduralData(const ProceduralData& proceduralData) { @@ -119,7 +118,7 @@ void Procedural::setProceduralData(const ProceduralData& proceduralData) { _dirty = true; _enabled = false; - if (proceduralData.version != _data.version ) { + if (proceduralData.version != _data.version) { _data.version = proceduralData.version; _shaderDirty = true; } @@ -144,7 +143,6 @@ void Procedural::setProceduralData(const ProceduralData& proceduralData) { _channels[channel] = textureCache->getTexture(QUrl()); } } - _channelsDirty = true; } if (proceduralData.shaderUrl != _data.shaderUrl) { @@ -212,23 +210,6 @@ bool Procedural::isReady() const { return true; } -std::string Procedural::replaceProceduralBlock(const std::string& fragmentSource) { - std::string result = fragmentSource; - auto replaceIndex = result.find(PROCEDURAL_VERSION); - if (replaceIndex != std::string::npos) { - if (_data.version == 1) { - result.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V1 1"); - } else if (_data.version == 2) { - result.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V2 1"); - } - } - replaceIndex = result.find(PROCEDURAL_BLOCK); - if (replaceIndex != std::string::npos) { - result.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); - } - return result; -} - void Procedural::prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, @@ -256,19 +237,21 @@ void Procedural::prepare(gpu::Batch& batch, } // Build the fragment shader - std::string opaqueShaderSource = replaceProceduralBlock(_opaquefragmentSource.getCode()); - auto opaqueReflection = _opaquefragmentSource.getReflection(); - auto& opaqueUniforms = opaqueReflection[gpu::Shader::BindingType::UNIFORM]; - std::string transparentShaderSource = replaceProceduralBlock(_transparentfragmentSource.getCode()); - auto transparentReflection = _transparentfragmentSource.getReflection(); - auto& transparentUniforms = transparentReflection[gpu::Shader::BindingType::UNIFORM]; + _opaqueFragmentSource.replacements.clear(); + if (_data.version == 1) { + _opaqueFragmentSource.replacements[PROCEDURAL_VERSION] = "#define PROCEDURAL_V1 1"; + } else if (_data.version == 2) { + _opaqueFragmentSource.replacements[PROCEDURAL_VERSION] = "#define PROCEDURAL_V2 1"; + } + _opaqueFragmentSource.replacements[PROCEDURAL_BLOCK] = _shaderSource.toStdString(); + _transparentFragmentSource.replacements = _opaqueFragmentSource.replacements; // Set any userdata specified uniforms int customSlot = procedural::slot::uniform::Custom; for (const auto& key : _data.uniforms.keys()) { std::string uniformName = key.toLocal8Bit().data(); - opaqueUniforms[uniformName] = customSlot; - transparentUniforms[uniformName] = customSlot; + _opaqueFragmentSource.reflection.uniforms[uniformName] = customSlot; + _transparentFragmentSource.reflection.uniforms[uniformName] = customSlot; ++customSlot; } @@ -276,18 +259,18 @@ void Procedural::prepare(gpu::Batch& batch, // qCDebug(procedural) << "FragmentShader:\n" << fragmentShaderSource.c_str(); // TODO: THis is a simple fix, we need a cleaner way to provide the "hosting" program for procedural custom shaders to be defined together with the required bindings. - _opaqueFragmentShader = gpu::Shader::createPixel({ opaqueShaderSource, opaqueReflection }); + _opaqueFragmentShader = gpu::Shader::createPixel(_opaqueFragmentSource); _opaqueShader = gpu::Shader::createProgram(_vertexShader, _opaqueFragmentShader); - if (!transparentShaderSource.empty() && transparentShaderSource != opaqueShaderSource) { - _transparentFragmentShader = gpu::Shader::createPixel({ transparentShaderSource, transparentReflection }); + _opaquePipeline = gpu::Pipeline::create(_opaqueShader, _opaqueState); + if (_transparentFragmentSource.valid()) { + _transparentFragmentShader = gpu::Shader::createPixel(_transparentFragmentSource); _transparentShader = gpu::Shader::createProgram(_vertexShader, _transparentFragmentShader); + _transparentPipeline = gpu::Pipeline::create(_transparentShader, _transparentState); } else { _transparentFragmentShader = _opaqueFragmentShader; _transparentShader = _opaqueShader; + _transparentPipeline = _opaquePipeline; } - - _opaquePipeline = gpu::Pipeline::create(_opaqueShader, _opaqueState); - _transparentPipeline = gpu::Pipeline::create(_transparentShader, _transparentState); _start = usecTimestampNow(); _frameCount = 0; } @@ -299,12 +282,8 @@ void Procedural::prepare(gpu::Batch& batch, setupUniforms(transparent); } - if (_shaderDirty || _uniformsDirty || _channelsDirty || _prevTransparent != transparent) { - setupChannels(_shaderDirty || _uniformsDirty, transparent); - } - _prevTransparent = transparent; - _shaderDirty = _uniformsDirty = _channelsDirty = false; + _shaderDirty = _uniformsDirty = false; for (auto lambda : _uniforms) { lambda(batch); @@ -331,16 +310,10 @@ void Procedural::prepare(gpu::Batch& batch, void Procedural::setupUniforms(bool transparent) { _uniforms.clear(); - auto& pipeline = transparent ? _transparentShader : _opaqueShader; - const auto& uniformSlots = pipeline->getUniforms(); auto customUniformCount = _data.uniforms.keys().size(); - // Set any userdata specified uniforms for (int i = 0; i < customUniformCount; ++i) { int slot = procedural::slot::uniform::Custom + i; - if (!uniformSlots.isValid(slot)) { - continue; - } QString key = _data.uniforms.keys().at(i); std::string uniformName = key.toLocal8Bit().data(); QJsonValue value = _data.uniforms[key]; @@ -390,73 +363,42 @@ void Procedural::setupUniforms(bool transparent) { } } - if (uniformSlots.isValid(procedural::slot::uniform::Time)) { - _uniforms.push_back([=](gpu::Batch& batch) { + _uniforms.push_back([=](gpu::Batch& batch) { + // Time and position + { // Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds float time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND; - batch._glUniform(procedural::slot::uniform::Time, time); - }); - } + _standardInputs.posAndTime = vec4(_entityPosition, time); + } - if (uniformSlots.isValid(procedural::slot::uniform::Date)) { - _uniforms.push_back([=](gpu::Batch& batch) { + // Date + { QDateTime now = QDateTime::currentDateTimeUtc(); QDate date = now.date(); QTime time = now.time(); - vec4 v; - v.x = date.year(); + _standardInputs.date.x = date.year(); // Shadertoy month is 0 based - v.y = date.month() - 1; + _standardInputs.date.y = date.month() - 1; // But not the day... go figure - v.z = date.day(); + _standardInputs.date.z = date.day(); float fractSeconds = (time.msec() / 1000.0f); - v.w = (time.hour() * 3600) + (time.minute() * 60) + time.second() + fractSeconds; - batch._glUniform(procedural::slot::uniform::Date, v); - }); - } - - if (uniformSlots.isValid(procedural::slot::uniform::FrameCount)) { - _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform(procedural::slot::uniform::FrameCount, ++_frameCount); }); - } - - if (uniformSlots.isValid(procedural::slot::uniform::Scale)) { - // FIXME move into the 'set once' section, since this doesn't change over time - _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform(procedural::slot::uniform::Scale, _entityDimensions); }); - } - - if (uniformSlots.isValid(procedural::slot::uniform::Orientation)) { - // FIXME move into the 'set once' section, since this doesn't change over time - _uniforms.push_back( - [=](gpu::Batch& batch) { batch._glUniform(procedural::slot::uniform::Orientation, _entityOrientation); }); - } - - if (uniformSlots.isValid(procedural::slot::uniform::Position)) { - // FIXME move into the 'set once' section, since this doesn't change over time - _uniforms.push_back( - [=](gpu::Batch& batch) { batch._glUniform(procedural::slot::uniform::Orientation, _entityPosition); }); - } -} - -void Procedural::setupChannels(bool shouldCreate, bool transparent) { - auto& pipeline = transparent ? _transparentShader : _opaqueShader; - const auto& uniformSlots = pipeline->getUniforms(); - - if (uniformSlots.isValid(procedural::slot::uniform::ChannelResolution)) { - if (!shouldCreate) { - // Instead of modifying the last element, just remove and recreate it. - _uniforms.pop_back(); + _standardInputs.date.w = (time.hour() * 3600) + (time.minute() * 60) + time.second() + fractSeconds; } - _uniforms.push_back([=](gpu::Batch& batch) { - vec3 channelSizes[MAX_PROCEDURAL_TEXTURE_CHANNELS]; - for (size_t i = 0; i < MAX_PROCEDURAL_TEXTURE_CHANNELS; ++i) { - if (_channels[i]) { - channelSizes[i] = vec3(_channels[i]->getWidth(), _channels[i]->getHeight(), 1.0); - } + + _standardInputs.scaleAndCount = vec4(_entityDimensions, ++_frameCount); + _standardInputs.orientation = mat4(_entityOrientation); + + for (size_t i = 0; i < MAX_PROCEDURAL_TEXTURE_CHANNELS; ++i) { + if (_channels[i]) { + _standardInputs.resolution[i] = vec4(_channels[i]->getWidth(), _channels[i]->getHeight(), 1.0f, 1.0f); + } else { + _standardInputs.resolution[i] = vec4(1.0f); } - batch._glUniform3fv(procedural::slot::uniform::ChannelResolution, MAX_PROCEDURAL_TEXTURE_CHANNELS, - &channelSizes[0].x); - }); - } + } + + _standardInputsBuffer->setSubData(0, _standardInputs); + batch.setUniformBuffer(0, _standardInputsBuffer, 0, sizeof(StandardInputs)); + }); } glm::vec4 Procedural::getColor(const glm::vec4& entityColor) { diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 973b323f60..c92725b61b 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -65,14 +65,22 @@ public: void setDoesFade(bool doesFade) { _doesFade = doesFade; } gpu::Shader::Source _vertexSource; - gpu::Shader::Source _opaquefragmentSource; - gpu::Shader::Source _transparentfragmentSource; + gpu::Shader::Source _opaqueFragmentSource; + gpu::Shader::Source _transparentFragmentSource; gpu::StatePointer _opaqueState { std::make_shared() }; gpu::StatePointer _transparentState { std::make_shared() }; protected: + struct StandardInputs { + vec4 date; + vec4 posAndTime; + vec4 scaleAndCount; + mat4 orientation; + vec4 resolution[4]; + }; + // Procedural metadata ProceduralData _data; @@ -88,13 +96,14 @@ protected: bool _dirty { false }; bool _shaderDirty { true }; bool _uniformsDirty { true }; - bool _channelsDirty { true }; // Rendering objects UniformLambdas _uniforms; NetworkTexturePointer _channels[MAX_PROCEDURAL_TEXTURE_CHANNELS]; gpu::PipelinePointer _opaquePipeline; gpu::PipelinePointer _transparentPipeline; + StandardInputs _standardInputs; + gpu::BufferPointer _standardInputsBuffer; gpu::ShaderPointer _vertexShader; gpu::ShaderPointer _opaqueFragmentShader; gpu::ShaderPointer _transparentFragmentShader; @@ -109,9 +118,6 @@ protected: private: // This should only be called from the render thread, as it shares data with Procedural::prepare void setupUniforms(bool transparent); - void setupChannels(bool shouldCreate, bool transparent); - - std::string replaceProceduralBlock(const std::string& fragmentSource); mutable quint64 _fadeStartTime { 0 }; mutable bool _hasStartedFade { false }; diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh index c36f2da1d3..fbfba81329 100644 --- a/libraries/procedural/src/procedural/ProceduralCommon.slh +++ b/libraries/procedural/src/procedural/ProceduralCommon.slh @@ -14,18 +14,51 @@ <$declareStandardCameraTransform()$> -#define PROCEDURAL 1 +#ifdef GL_EXT_shader_non_constant_global_initializers +#extension GL_EXT_shader_non_constant_global_initializers : enable +#endif -//PROCEDURAL_VERSION +LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL0) uniform sampler2D iChannel0; +LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL1) uniform sampler2D iChannel1; +LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL2) uniform sampler2D iChannel2; +LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL3) uniform sampler2D iChannel3; -#ifdef PROCEDURAL_V1 +struct StandardInputs { + vec4 date; + vec4 posAndTime; + vec4 scaleAndCount; + mat4 orientation; + vec4 resolution[4]; +}; + + +LAYOUT(binding=0) uniform standardInputsBuffer { + StandardInputs params; +}; // shader playback time (in seconds) -layout(location=PROCEDURAL_UNIFORM_TIME) uniform float iGlobalTime; -// the dimensions of the object being rendered -layout(location=PROCEDURAL_UNIFORM_SCALE) uniform vec3 iWorldScale; +float iGlobalTime = params.posAndTime.w; + +vec4 iDate = params.date; + +int iFrameCount = int(params.scaleAndCount.w); + +// the position of the object being rendered +vec3 iWorldPosition = params.posAndTime.xyz; + +// the dimensions of the object being rendered +vec3 iWorldScale = params.scaleAndCount.xyz; + +// the orientation of the object being rendered +mat3 iWorldOrientation = mat3(params.orientation); + +vec3 iChannelResolution[4] = vec3[4]( + params.resolution[0].xyz, + params.resolution[1].xyz, + params.resolution[2].xyz, + params.resolution[3].xyz +); -#else // Unimplemented uniforms // Resolution doesn't make sense in the VR context @@ -37,20 +70,9 @@ const float iSampleRate = 1.0; // No support for video input const vec4 iChannelTime = vec4(0.0); +#define PROCEDURAL 1 -layout(location=PROCEDURAL_UNIFORM_TIME) uniform float iGlobalTime; // shader playback time (in seconds) -layout(location=PROCEDURAL_UNIFORM_DATE) uniform vec4 iDate; -layout(location=PROCEDURAL_UNIFORM_FRAME_COUNT) uniform int iFrameCount; -layout(location=PROCEDURAL_UNIFORM_POSITION) uniform vec3 iWorldPosition; // the position of the object being rendered -layout(location=PROCEDURAL_UNIFORM_SCALE) uniform vec3 iWorldScale; // the dimensions of the object being rendered -layout(location=PROCEDURAL_UNIFORM_ORIENTATION) uniform mat3 iWorldOrientation; // the orientation of the object being rendered -layout(location=PROCEDURAL_UNIFORM_CHANNEL_RESOLUTION) uniform vec3 iChannelResolution[4]; -layout(binding=PROCEDURAL_TEXTURE_CHANNEL0) uniform sampler2D iChannel0; -layout(binding=PROCEDURAL_TEXTURE_CHANNEL1) uniform sampler2D iChannel1; -layout(binding=PROCEDURAL_TEXTURE_CHANNEL2) uniform sampler2D iChannel2; -layout(binding=PROCEDURAL_TEXTURE_CHANNEL3) uniform sampler2D iChannel3; - -#endif +//PROCEDURAL_VERSION // hack comment for extra whitespace diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index 0addb57fcf..ea5be23eb8 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -19,7 +19,7 @@ ProceduralSkybox::ProceduralSkybox() : graphics::Skybox() { _procedural._vertexSource = gpu::Shader::createVertex(shader::graphics::vertex::skybox)->getSource(); - _procedural._opaquefragmentSource = gpu::Shader::createPixel(shader::procedural::fragment::proceduralSkybox)->getSource(); + _procedural._opaqueFragmentSource = shader::Source::get(shader::procedural::fragment::proceduralSkybox); // Adjust the pipeline state for background using the stencil test _procedural.setDoesFade(false); // Must match PrepareStencil::STENCIL_BACKGROUND diff --git a/libraries/procedural/src/procedural/ShaderConstants.h b/libraries/procedural/src/procedural/ShaderConstants.h index bfbf2a2691..cd0d997050 100644 --- a/libraries/procedural/src/procedural/ShaderConstants.h +++ b/libraries/procedural/src/procedural/ShaderConstants.h @@ -14,14 +14,6 @@ #ifndef PROCEDURAL_SHADER_CONSTANTS_H #define PROCEDURAL_SHADER_CONSTANTS_H -#define PROCEDURAL_UNIFORM_TIME 200 -#define PROCEDURAL_UNIFORM_DATE 201 -#define PROCEDURAL_UNIFORM_FRAME_COUNT 202 -#define PROCEDURAL_UNIFORM_POSITION 203 -#define PROCEDURAL_UNIFORM_SCALE 204 -#define PROCEDURAL_UNIFORM_ORIENTATION 205 -// Additional space because orientation will take up 3-4 locations, being a matrix -#define PROCEDURAL_UNIFORM_CHANNEL_RESOLUTION 209 #define PROCEDURAL_UNIFORM_CUSTOM 220 #define PROCEDURAL_TEXTURE_CHANNEL0 0 @@ -33,15 +25,9 @@ namespace procedural { namespace slot { + namespace uniform { enum Uniform { - Time = PROCEDURAL_UNIFORM_TIME, - Date = PROCEDURAL_UNIFORM_DATE, - FrameCount = PROCEDURAL_UNIFORM_FRAME_COUNT, - Position = PROCEDURAL_UNIFORM_POSITION, - Scale = PROCEDURAL_UNIFORM_SCALE, - Orientation = PROCEDURAL_UNIFORM_ORIENTATION, - ChannelResolution = PROCEDURAL_UNIFORM_CHANNEL_RESOLUTION, Custom = PROCEDURAL_UNIFORM_CUSTOM, }; } diff --git a/libraries/procedural/src/procedural/proceduralSkybox.slf b/libraries/procedural/src/procedural/proceduralSkybox.slf index e18b7abef6..12e8de9dc3 100644 --- a/libraries/procedural/src/procedural/proceduralSkybox.slf +++ b/libraries/procedural/src/procedural/proceduralSkybox.slf @@ -12,13 +12,13 @@ // <@include graphics/ShaderConstants.h@> -layout(binding=GRAPHICS_TEXTURE_SKYBOX) uniform samplerCube cubeMap; +LAYOUT(binding=GRAPHICS_TEXTURE_SKYBOX) uniform samplerCube cubeMap; struct Skybox { vec4 color; }; -layout(binding=GRAPHICS_BUFFER_SKYBOX_PARAMS) uniform skyboxBuffer { +LAYOUT(binding=GRAPHICS_BUFFER_SKYBOX_PARAMS) uniform skyboxBuffer { Skybox skybox; }; @@ -28,9 +28,13 @@ layout(location=0) out vec4 _fragColor; <@include procedural/ProceduralCommon.slh@> #line 1001 -//PROCEDURAL_BLOCK +//PROCEDURAL_BLOCK_BEGIN +vec3 getSkyboxColor() { + return vec3(abs(sin(iGlobalTime / 5.0)), 1.0, 0.0); +} +//PROCEDURAL_BLOCK_END -#line 2033 +#line 2038 void main(void) { vec3 color = getSkyboxColor(); // Protect from NaNs and negative values diff --git a/libraries/procedural/src/procedural/proceduralSkybox.slp b/libraries/procedural/src/procedural/proceduralSkybox.slp new file mode 100644 index 0000000000..5247547850 --- /dev/null +++ b/libraries/procedural/src/procedural/proceduralSkybox.slp @@ -0,0 +1 @@ +VERTEX graphics::vertex::skybox \ No newline at end of file diff --git a/libraries/render-utils/src/Blendshape.slh b/libraries/render-utils/src/Blendshape.slh index df62af5a77..73a561c73f 100644 --- a/libraries/render-utils/src/Blendshape.slh +++ b/libraries/render-utils/src/Blendshape.slh @@ -10,13 +10,13 @@ <@func declareBlendshape(USE_NORMAL, USE_TANGENT)@> -#if defined(GPU_GL410) -layout(binding=0) uniform samplerBuffer blendshapeOffsetsBuffer; +#if !defined(GPU_SSBO_TRANSFORM_OBJECT) +LAYOUT(binding=0) uniform samplerBuffer blendshapeOffsetsBuffer; uvec4 getPackedBlendshapeOffset(int i) { return floatBitsToUint(texelFetch(blendshapeOffsetsBuffer, i)); } #else -layout(std140, binding=0) buffer blendshapeOffsetsBuffer { +LAYOUT_STD140(binding=0) buffer blendshapeOffsetsBuffer { uvec4 _packedBlendshapeOffsets[]; }; uvec4 getPackedBlendshapeOffset(int i) { diff --git a/libraries/render-utils/src/BloomApply.slf b/libraries/render-utils/src/BloomApply.slf index a53894de60..dcdb989780 100644 --- a/libraries/render-utils/src/BloomApply.slf +++ b/libraries/render-utils/src/BloomApply.slf @@ -12,11 +12,11 @@ <@include BloomApply.shared.slh@> <@include render-utils/ShaderConstants.h@> -layout(binding=0) uniform sampler2D blurMap0; -layout(binding=1) uniform sampler2D blurMap1; -layout(binding=2) uniform sampler2D blurMap2; +LAYOUT(binding=0) uniform sampler2D blurMap0; +LAYOUT(binding=1) uniform sampler2D blurMap1; +LAYOUT(binding=2) uniform sampler2D blurMap2; -layout(std140, binding=RENDER_UTILS_BUFFER_BLOOM_PARAMS) uniform parametersBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_BLOOM_PARAMS) uniform parametersBuffer { Parameters parameters; }; diff --git a/libraries/render-utils/src/BloomThreshold.slf b/libraries/render-utils/src/BloomThreshold.slf index 47a1fb0d9f..bbf863994f 100644 --- a/libraries/render-utils/src/BloomThreshold.slf +++ b/libraries/render-utils/src/BloomThreshold.slf @@ -12,8 +12,8 @@ <@include BloomThreshold.shared.slh@> <@include render-utils/ShaderConstants.h@> -layout(binding=RENDER_UTILS_TEXTURE_BLOOM_COLOR) uniform sampler2D colorMap; -layout(std140, binding=RENDER_UTILS_BUFFER_BLOOM_PARAMS) uniform parametersBuffer { +LAYOUT(binding=RENDER_UTILS_TEXTURE_BLOOM_COLOR) uniform sampler2D colorMap; +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_BLOOM_PARAMS) uniform parametersBuffer { Parameters parameters; }; diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index be9e75a9a5..9597ce1052 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -243,7 +243,7 @@ static const std::string DEFAULT_CUSTOM_SHADER{ " }" }; -static std::string getFileContent(std::string fileName, std::string defaultContent = std::string()) { +static std::string getFileContent(const std::string& fileName, const std::string& defaultContent = std::string()) { QFile customFile(QString::fromStdString(fileName)); if (customFile.open(QIODevice::ReadOnly)) { return customFile.readAll().toStdString(); @@ -270,7 +270,7 @@ DebugDeferredBuffer::~DebugDeferredBuffer() { } } -std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, std::string customFile) { +std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, const std::string& customFile) { switch (mode) { case AlbedoMode: return DEFAULT_ALBEDO_SHADER; @@ -334,7 +334,7 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, std::string cust return std::string(); } -bool DebugDeferredBuffer::pipelineNeedsUpdate(Mode mode, std::string customFile) const { +bool DebugDeferredBuffer::pipelineNeedsUpdate(Mode mode, const std::string& customFile) const { if (mode != CustomMode) { return !_pipelines[mode]; } @@ -351,19 +351,17 @@ bool DebugDeferredBuffer::pipelineNeedsUpdate(Mode mode, std::string customFile) return true; } -const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::string customFile) { +const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, const std::string& customFile) { if (pipelineNeedsUpdate(mode, customFile)) { - static const auto FRAGMENT_SHADER_SOURCE = - gpu::Shader::createPixel(shader::render_utils::fragment::debug_deferred_buffer)->getSource(); - static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; - static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER_SOURCE.getCode().find(SOURCE_PLACEHOLDER); - Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, "Could not find source placeholder"); + static_assert(shader::render_utils::program::debug_deferred_buffer != 0, "Validate debug deferred program"); - auto bakedFragmentShader = FRAGMENT_SHADER_SOURCE.getCode(); - bakedFragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), getShaderSourceCode(mode, customFile)); + static const std::string REPLACEMENT_MARKER{ "//SOURCE_PLACEHOLDER" }; + shader::Source resolvedFragmentSource; + resolvedFragmentSource = shader::Source::get(shader::render_utils::fragment::debug_deferred_buffer); + resolvedFragmentSource.replacements[REPLACEMENT_MARKER] = getShaderSourceCode(mode, customFile); const auto vs = gpu::Shader::createVertex(shader::render_utils::vertex::debug_deferred_buffer); - const auto ps = gpu::Shader::createPixel({ bakedFragmentShader, FRAGMENT_SHADER_SOURCE.getReflection() }); + const auto ps = gpu::Shader::createPixel(resolvedFragmentSource); const auto program = gpu::Shader::createProgram(vs, ps); auto pipeline = gpu::Pipeline::create(program, std::make_shared()); diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index dba3ff8532..cdaf5db83a 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -112,9 +112,9 @@ private: using StandardPipelines = std::array; using CustomPipelines = std::unordered_map; - bool pipelineNeedsUpdate(Mode mode, std::string customFile = std::string()) const; - const gpu::PipelinePointer& getPipeline(Mode mode, std::string customFile = std::string()); - std::string getShaderSourceCode(Mode mode, std::string customFile = std::string()); + bool pipelineNeedsUpdate(Mode mode, const std::string& customFile = std::string()) const; + const gpu::PipelinePointer& getPipeline(Mode mode, const std::string& customFile = std::string()); + std::string getShaderSourceCode(Mode mode, const std::string& customFile = std::string()); ParametersBuffer _parameters; StandardPipelines _pipelines; diff --git a/libraries/render-utils/src/DeferredBufferRead.slh b/libraries/render-utils/src/DeferredBufferRead.slh index e5a7c39d54..f3b8c0404a 100644 --- a/libraries/render-utils/src/DeferredBufferRead.slh +++ b/libraries/render-utils/src/DeferredBufferRead.slh @@ -17,23 +17,23 @@ // See DeferredShader_MapSlot in DeferredLightingEffect.cpp for constants // the albedo texture -layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_COLOR) uniform sampler2D albedoMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_DEFERRRED_COLOR) uniform sampler2D albedoMap; // the normal texture -layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_NORMAL) uniform sampler2D normalMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_DEFERRRED_NORMAL) uniform sampler2D normalMap; // the specular texture -layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_SPECULAR) uniform sampler2D specularMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_DEFERRRED_SPECULAR) uniform sampler2D specularMap; // the depth texture -layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_DEPTH) uniform sampler2D depthMap; -layout(binding=RENDER_UTILS_TEXTURE_DEFERRRED_LINEAR_Z_EYE) uniform sampler2D linearZeyeMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_DEFERRRED_DEPTH) uniform sampler2D depthMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_DEFERRRED_LINEAR_Z_EYE) uniform sampler2D linearZeyeMap; // the obscurance texture -layout(binding=RENDER_UTILS_TEXTURE_DEFERRED_OBSCURANCE) uniform sampler2D obscuranceMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_DEFERRED_OBSCURANCE) uniform sampler2D obscuranceMap; // the lighting texture -layout(binding=RENDER_UTILS_TEXTURE_DEFERRED_LIGHTING) uniform sampler2D lightingMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_DEFERRED_LIGHTING) uniform sampler2D lightingMap; struct DeferredFragment { @@ -170,14 +170,14 @@ DeferredFragment unpackDeferredFragment(DeferredFrameTransform deferredTransform <@func declareDeferredCurvature()@> // the curvature texture -layout(binding=RENDER_UTILS_TEXTURE_DEFERRED_CURVATURE) uniform sampler2D curvatureMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_DEFERRED_CURVATURE) uniform sampler2D curvatureMap; vec4 fetchCurvature(vec2 texcoord) { return texture(curvatureMap, texcoord); } // the curvature texture -layout(binding=RENDER_UTILS_TEXTURE_DEFERRED_DIFFUSED_CURVATURE) uniform sampler2D diffusedCurvatureMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_DEFERRED_DIFFUSED_CURVATURE) uniform sampler2D diffusedCurvatureMap; vec4 fetchDiffusedCurvature(vec2 texcoord) { return texture(diffusedCurvatureMap, texcoord); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index a10949e0e6..8cf56ec7ad 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -44,7 +44,7 @@ using namespace render; struct LightLocations { bool shadowTransform{ false }; void initialize(const gpu::ShaderPointer& program) { - shadowTransform = program->getUniformBuffers().isValid(ru::Buffer::ShadowParams); + shadowTransform = program->getReflection().validUniformBuffer(ru::Buffer::ShadowParams); } }; diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index 19986805f6..8a8805e928 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -24,7 +24,7 @@ struct CameraCorrection { mat4 _prevViewInverse; }; -layout(binding=GPU_BUFFER_CAMERA_CORRECTION) uniform cameraCorrectionBuffer { +LAYOUT(binding=GPU_BUFFER_CAMERA_CORRECTION) uniform cameraCorrectionBuffer { CameraCorrection cameraCorrection; }; @@ -42,7 +42,7 @@ struct DeferredFrameTransform { mat4 _invProjectionUnJittered[2]; }; -layout(binding=RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM) uniform deferredFrameTransformBuffer { +LAYOUT(binding=RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM) uniform deferredFrameTransformBuffer { DeferredFrameTransform frameTransform; }; diff --git a/libraries/render-utils/src/Fade.slh b/libraries/render-utils/src/Fade.slh index 47347ba135..a7523f969b 100644 --- a/libraries/render-utils/src/Fade.slh +++ b/libraries/render-utils/src/Fade.slh @@ -19,12 +19,12 @@ <@include FadeObjectParams.shared.slh@> // See ShapePipeline::Slot::BUFFER in ShapePipeline.h -layout(std140, binding=RENDER_UTILS_BUFFER_FADE_PARAMS) uniform fadeParametersBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_FADE_PARAMS) uniform fadeParametersBuffer { FadeParameters fadeParameters[CATEGORY_COUNT]; }; // See ShapePipeline::Slot::MAP in ShapePipeline.h -layout(binding=RENDER_UTILS_TEXTURE_FADE_MASK) uniform sampler2D fadeMaskMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_FADE_MASK) uniform sampler2D fadeMaskMap; vec3 getNoiseInverseSize(int category) { return fadeParameters[category]._noiseInvSizeAndLevel.xyz; @@ -117,7 +117,7 @@ void applyFade(FadeObjectParams params, vec3 position, out vec3 emissive) { <@func declareFadeFragmentUniform()@> -layout(std140, binding=RENDER_UTILS_BUFFER_FADE_OBJECT_PARAMS) uniform fadeObjectParametersBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_FADE_OBJECT_PARAMS) uniform fadeObjectParametersBuffer { FadeObjectParams fadeObjectParams; }; diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index bb3c0bc769..8d90b4c816 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -21,7 +21,7 @@ <@include Haze.slh@> -layout(binding=RENDER_UTILS_TEXTURE_HAZE_LINEAR_DEPTH) uniform sampler2D linearDepthMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_HAZE_LINEAR_DEPTH) uniform sampler2D linearDepthMap; vec4 unpackPositionFromZeye(vec2 texcoord) { float Zeye = -texture(linearDepthMap, texcoord).x; diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index b7bcfcefcd..a7654da8d2 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -39,7 +39,7 @@ struct HazeParams { }; // See ShapePipeline::Slot::BUFFER in ShapePipeline.h -layout(std140, binding=RENDER_UTILS_BUFFER_HAZE_PARAMS) uniform hazeBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_HAZE_PARAMS) uniform hazeBuffer { HazeParams hazeParams; }; diff --git a/libraries/render-utils/src/Highlight.slh b/libraries/render-utils/src/Highlight.slh index 885df34d26..264b57acbb 100644 --- a/libraries/render-utils/src/Highlight.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -15,12 +15,12 @@ <@include Highlight_shared.slh@> -layout(std140, binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_HIGHLIGHT_PARAMS) uniform highlightParamsBuffer { HighlightParameters params; }; -layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH) uniform sampler2D sceneDepthMap; -layout(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH) uniform sampler2D highlightedDepthMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_SCENE_DEPTH) uniform sampler2D sceneDepthMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_HIGHLIGHT_DEPTH) uniform sampler2D highlightedDepthMap; layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 2d4aee7880..0cb08971ff 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -402,36 +402,31 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons } void DebugHighlight::initializePipelines() { - static const auto FRAGMENT_SHADER_SOURCE = gpu::Shader::createPixel(shader::render_utils::fragment::debug_deferred_buffer)->getSource(); - static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; - static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER_SOURCE.getCode().find(SOURCE_PLACEHOLDER); - Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, - "Could not find source placeholder"); - - auto state = std::make_shared(); - state->setDepthTest(gpu::State::DepthTest(false, false)); - state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); - state->setColorWriteMask(true, true, true, true); - - const auto vs = gpu::Shader::createVertex(shader::render_utils::vertex::debug_deferred_buffer); - + static const std::string REPLACEMENT_MARKER{ "//SOURCE_PLACEHOLDER" }; // Depth shader - { - static const std::string DEPTH_SHADER{ R"SHADER( + static const std::string DEPTH_SHADER{ R"SHADER( vec4 getFragmentColor() { float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; Zdb = 1.0-(1.0-Zdb)*100; return vec4(Zdb, Zdb, Zdb, 1.0); } )SHADER" }; + static const auto& vs = gpu::Shader::createVertex(shader::render_utils::vertex::debug_deferred_buffer); - auto fragmentShader = FRAGMENT_SHADER_SOURCE.getCode(); - fragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), DEPTH_SHADER); - const auto ps = gpu::Shader::createPixel({ fragmentShader, FRAGMENT_SHADER_SOURCE.getReflection() }); - const auto program = gpu::Shader::createProgram(vs, ps); - _depthPipeline = gpu::Pipeline::create(program, state); - } + gpu::Shader::Source fragmentSource; + fragmentSource = gpu::Shader::Source::get(shader::render_utils::fragment::debug_deferred_buffer); + fragmentSource.replacements[REPLACEMENT_MARKER] = DEPTH_SHADER; + + const auto ps = gpu::Shader::createPixel(fragmentSource); + const auto program = gpu::Shader::createProgram(vs, ps); + + auto state = std::make_shared(); + state->setDepthTest(gpu::State::DepthTest(false, false)); + state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); + state->setColorWriteMask(true, true, true, true); + + _depthPipeline = gpu::Pipeline::create(program, state); } const gpu::PipelinePointer& DebugHighlight::getDepthPipeline() { diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv index 2ecebdea51..17ef7b6e07 100644 --- a/libraries/render-utils/src/Highlight_aabox.slv +++ b/libraries/render-utils/src/Highlight_aabox.slv @@ -22,8 +22,8 @@ struct ItemBound { vec4 boundDim_s; }; -#if defined(GPU_GL410) -layout(binding=0) uniform samplerBuffer ssbo0Buffer; +#if !defined(GPU_SSBO_TRANSFORM_OBJECT) +LAYOUT(binding=0) uniform samplerBuffer ssbo0Buffer; ItemBound getItemBound(int i) { int offset = 2 * i; ItemBound bound; @@ -32,7 +32,7 @@ ItemBound getItemBound(int i) { return bound; } #else -layout(std140, binding=0) buffer ssbo0Buffer { +LAYOUT_STD140(binding=0) buffer ssbo0Buffer { ItemBound bounds[]; }; ItemBound getItemBound(int i) { @@ -45,7 +45,7 @@ struct HighlightParameters { vec2 outlineWidth; }; -layout(std140, binding=0) uniform parametersBuffer { +LAYOUT_STD140(binding=0) uniform parametersBuffer { HighlightParameters _parameters; }; diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh index 797595bf47..4ea9c0cd4c 100644 --- a/libraries/render-utils/src/LightAmbient.slh +++ b/libraries/render-utils/src/LightAmbient.slh @@ -9,7 +9,7 @@ <@include render-utils/ShaderConstants.h@> <@func declareSkyboxMap()@> // declareSkyboxMap -layout(binding=RENDER_UTILS_TEXTURE_SKYBOX) uniform samplerCube skyboxMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SKYBOX) uniform samplerCube skyboxMap; vec4 evalSkyboxLight(vec3 direction, float lod) { // textureQueryLevels is not available until #430, so we require explicit lod diff --git a/libraries/render-utils/src/LightClusterGrid.slh b/libraries/render-utils/src/LightClusterGrid.slh index 8f57169ace..62af92e6ce 100644 --- a/libraries/render-utils/src/LightClusterGrid.slh +++ b/libraries/render-utils/src/LightClusterGrid.slh @@ -24,7 +24,7 @@ struct FrustumGrid { mat4 eyeToWorldMat; }; -layout(std140, binding=RENDER_UTILS_BUFFER_LIGHT_CLUSTER_FRUSTUM_GRID) uniform frustumGridBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_LIGHT_CLUSTER_FRUSTUM_GRID) uniform frustumGridBuffer { FrustumGrid frustumGrid; }; @@ -60,11 +60,11 @@ float projection_getFar(mat4 projection) { #define GRID_FETCH_BUFFER(i) i!> <@endif@> -layout(std140, binding=RENDER_UTILS_BUFFER_LIGHT_CLUSTER_GRID) uniform clusterGridBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_LIGHT_CLUSTER_GRID) uniform clusterGridBuffer { GRID_INDEX_TYPE _clusterGridTable[GRID_NUM_ELEMENTS]; }; -layout(std140, binding=RENDER_UTILS_BUFFER_LIGHT_CLUSTER_CONTENT) uniform clusterContentBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_LIGHT_CLUSTER_CONTENT) uniform clusterContentBuffer { GRID_INDEX_TYPE _clusterGridContent[GRID_NUM_ELEMENTS]; }; diff --git a/libraries/render-utils/src/LightingModel.slh b/libraries/render-utils/src/LightingModel.slh index d10a52be60..61c74c7e50 100644 --- a/libraries/render-utils/src/LightingModel.slh +++ b/libraries/render-utils/src/LightingModel.slh @@ -23,7 +23,7 @@ struct LightingModel { }; // See DeferredShader_BufferSlot in DeferredLightingEffect.cpp -layout(binding=RENDER_UTILS_BUFFER_LIGHT_MODEL) uniform lightingModelBuffer{ +LAYOUT(binding=RENDER_UTILS_BUFFER_LIGHT_MODEL) uniform lightingModelBuffer{ LightingModel lightingModel; }; diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 5551bbdfa8..a3f06c8942 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -229,7 +229,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip } void initForwardPipelines(ShapePlumber& plumber) { - using namespace shader::render_utils::program; + using namespace shader::render_utils; using Key = render::ShapeKey; auto addPipelineBind = std::bind(&addPlumberPipeline, std::ref(plumber), _1, _2, _3, _4); @@ -244,33 +244,33 @@ void initForwardPipelines(ShapePlumber& plumber) { forceLightBatchSetter = true; // Simple Opaques - addPipeline(Key::Builder(), simple); - addPipeline(Key::Builder().withUnlit(), simpleUnlit); + addPipeline(Key::Builder(), program::forward_simple_textured); + addPipeline(Key::Builder().withUnlit(), program::forward_simple_textured_unlit); // Simple Translucents - addPipeline(Key::Builder().withTranslucent(), simpleTranslucent); - addPipeline(Key::Builder().withTranslucent().withUnlit(), simpleTranslucentUnlit); + addPipeline(Key::Builder().withTranslucent(), program::forward_simple_textured_transparent); + addPipeline(Key::Builder().withTranslucent().withUnlit(), program::simple_transparent_textured_unlit); // Opaques - addPipeline(Key::Builder().withMaterial(), forward_model); - addPipeline(Key::Builder().withMaterial().withUnlit(), forward_model_unlit); - addPipeline(Key::Builder().withMaterial().withTangents(), forward_model_translucent); + addPipeline(Key::Builder().withMaterial(), program::forward_model); + addPipeline(Key::Builder().withMaterial().withUnlit(), program::forward_model_unlit); + addPipeline(Key::Builder().withMaterial().withTangents(), program::forward_model_translucent); // Deformed Opaques - addPipeline(Key::Builder().withMaterial().withDeformed(), forward_deformed_model); - addPipeline(Key::Builder().withMaterial().withDeformed().withTangents(), forward_deformed_model_normal_map); - addPipeline(Key::Builder().withMaterial().withDeformed().withDualQuatSkinned(), forward_deformed_model_dq); - addPipeline(Key::Builder().withMaterial().withDeformed().withTangents().withDualQuatSkinned(), forward_deformed_model_normal_map_dq); + addPipeline(Key::Builder().withMaterial().withDeformed(), program::forward_deformed_model); + addPipeline(Key::Builder().withMaterial().withDeformed().withTangents(), program::forward_deformed_model_normal_map); + addPipeline(Key::Builder().withMaterial().withDeformed().withDualQuatSkinned(), program::forward_deformed_model_dq); + addPipeline(Key::Builder().withMaterial().withDeformed().withTangents().withDualQuatSkinned(), program::forward_deformed_model_normal_map_dq); // Translucents - addPipeline(Key::Builder().withMaterial().withTranslucent(), forward_model_translucent); - addPipeline(Key::Builder().withMaterial().withTranslucent().withTangents(), forward_model_normal_map_translucent); + addPipeline(Key::Builder().withMaterial().withTranslucent(), program::forward_model_translucent); + addPipeline(Key::Builder().withMaterial().withTranslucent().withTangents(), program::forward_model_normal_map_translucent); // Deformed Translucents - addPipeline(Key::Builder().withMaterial().withDeformed().withTranslucent(), forward_deformed_translucent); - addPipeline(Key::Builder().withMaterial().withDeformed().withTranslucent().withTangents(), forward_deformed_translucent_normal_map); - addPipeline(Key::Builder().withMaterial().withDeformed().withTranslucent().withDualQuatSkinned(), forward_deformed_translucent_dq); - addPipeline(Key::Builder().withMaterial().withDeformed().withTranslucent().withTangents().withDualQuatSkinned(), forward_deformed_translucent_normal_map_dq); + addPipeline(Key::Builder().withMaterial().withDeformed().withTranslucent(), program::forward_deformed_translucent); + addPipeline(Key::Builder().withMaterial().withDeformed().withTranslucent().withTangents(), program::forward_deformed_translucent_normal_map); + addPipeline(Key::Builder().withMaterial().withDeformed().withTranslucent().withDualQuatSkinned(), program::forward_deformed_translucent_dq); + addPipeline(Key::Builder().withMaterial().withDeformed().withTranslucent().withTangents().withDualQuatSkinned(), program::forward_deformed_translucent_normal_map_dq); forceLightBatchSetter = false; } diff --git a/libraries/render-utils/src/ShadingModel.slh b/libraries/render-utils/src/ShadingModel.slh index 6b0b7bca18..99aa01cc5e 100644 --- a/libraries/render-utils/src/ShadingModel.slh +++ b/libraries/render-utils/src/ShadingModel.slh @@ -15,7 +15,7 @@ <@func declareBeckmannSpecular()@> -layout(binding=RENDER_UTILS_TEXTURE_SSSC_SPECULAR_BECKMANN) uniform sampler2D scatteringSpecularBeckmann; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SSSC_SPECULAR_BECKMANN) uniform sampler2D scatteringSpecularBeckmann; float fetchSpecularBeckmann(float ndoth, float roughness) { return pow(2.0 * texture(scatteringSpecularBeckmann, vec2(ndoth, roughness)).r, 10.0); diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 5115a876fe..9506c9805d 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -19,7 +19,7 @@ #define SHADOW_SCREEN_SPACE_DITHER 1 // the shadow texture -layout(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowMaps; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowMaps; // Sample the shadowMap with PCF (built-in) float fetchShadow(int cascadeIndex, vec3 shadowTexcoord) { diff --git a/libraries/render-utils/src/ShadowCore.slh b/libraries/render-utils/src/ShadowCore.slh index 99c4b923f4..9819dac38c 100644 --- a/libraries/render-utils/src/ShadowCore.slh +++ b/libraries/render-utils/src/ShadowCore.slh @@ -13,7 +13,7 @@ <@include Shadows_shared.slh@> -layout(std140, binding=RENDER_UTILS_BUFFER_SHADOW_PARAMS) uniform shadowTransformBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_SHADOW_PARAMS) uniform shadowTransformBuffer { ShadowParameters shadow; }; diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index 622ca946c2..63246e85a1 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -18,7 +18,7 @@ const int MAX_CLUSTERS = 128; const int INDICES_PER_VERTEX = 4; -layout(std140, binding=GRAPHICS_BUFFER_SKINNING) uniform skinClusterBuffer { +LAYOUT_STD140(binding=GRAPHICS_BUFFER_SKINNING) uniform skinClusterBuffer { mat4 clusterMatrices[MAX_CLUSTERS]; }; diff --git a/libraries/render-utils/src/SubsurfaceScattering.cpp b/libraries/render-utils/src/SubsurfaceScattering.cpp index 84b51d626a..e004e66501 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.cpp +++ b/libraries/render-utils/src/SubsurfaceScattering.cpp @@ -421,6 +421,10 @@ void DebugSubsurfaceScattering::configure(const Config& config) { _showSpecularTable = config.showSpecularTable; _showCursorPixel = config.showCursorPixel; _debugCursorTexcoord = config.debugCursorTexcoord; + if (!_debugParams) { + _debugParams = std::make_shared(sizeof(glm::vec4), nullptr); + } + _debugParams->setSubData(0, _debugCursorTexcoord); } @@ -479,6 +483,10 @@ void DebugSubsurfaceScattering::run(const render::RenderContextPointer& renderCo assert(lightStage); // const auto light = DependencyManager::get()->getLightStage()->getLight(0); const auto light = lightStage->getLight(0); + if (!_debugParams) { + _debugParams = std::make_shared(sizeof(glm::vec4), nullptr); + _debugParams->setSubData(0, _debugCursorTexcoord); + } gpu::doInBatch("DebugSubsurfaceScattering::run", args->_context, [=](gpu::Batch& batch) { batch.enableStereo(false); @@ -521,9 +529,7 @@ void DebugSubsurfaceScattering::run(const render::RenderContextPointer& renderCo batch.setResourceTexture(ru::Texture::DeferredNormal, deferredFramebuffer->getDeferredNormalTexture()); batch.setResourceTexture(ru::Texture::DeferredColor, deferredFramebuffer->getDeferredColorTexture()); batch.setResourceTexture(ru::Texture::DeferredDepth, linearDepthTexture); - - - batch._glUniform2f(gpu::slot::uniform::Extra0, _debugCursorTexcoord.x, _debugCursorTexcoord.y); + batch.setUniformBuffer(1, _debugParams); batch.draw(gpu::TRIANGLE_STRIP, 4); } } diff --git a/libraries/render-utils/src/SubsurfaceScattering.h b/libraries/render-utils/src/SubsurfaceScattering.h index 780ce34d7f..e0073d23e8 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.h +++ b/libraries/render-utils/src/SubsurfaceScattering.h @@ -179,6 +179,7 @@ private: gpu::PipelinePointer _showLUTPipeline; gpu::PipelinePointer getShowLUTPipeline(); + gpu::BufferPointer _debugParams; bool _showProfile{ false }; bool _showLUT{ false }; bool _showSpecularTable{ false }; diff --git a/libraries/render-utils/src/SubsurfaceScattering.slh b/libraries/render-utils/src/SubsurfaceScattering.slh index 3d37f52e4d..66b3ab1ea0 100644 --- a/libraries/render-utils/src/SubsurfaceScattering.slh +++ b/libraries/render-utils/src/SubsurfaceScattering.slh @@ -56,7 +56,7 @@ vec3 generateProfile(vec2 uv) { <@func declareSubsurfaceScatteringProfileMap()@> -layout(binding=RENDER_UTILS_TEXTURE_SSSC_PROFILE) uniform sampler2D scatteringProfile; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SSSC_PROFILE) uniform sampler2D scatteringProfile; vec3 scatter(float r) { return texture(scatteringProfile, vec2(r * 0.5, 0.5)).rgb; @@ -104,7 +104,7 @@ vec3 integrate(float cosTheta, float skinRadius) { <@func declareSubsurfaceScatteringResource()@> -layout(binding=RENDER_UTILS_TEXTURE_SSSC_LUT) uniform sampler2D scatteringLUT; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SSSC_LUT) uniform sampler2D scatteringLUT; vec3 fetchBRDF(float LdotN, float curvature) { return texture(scatteringLUT, vec2( clamp(LdotN * 0.5 + 0.5, 0.0, 1.0), clamp(2.0 * curvature, 0.0, 1.0))).xyz; @@ -124,7 +124,7 @@ struct ScatteringParameters { vec4 debugFlags; }; -layout(binding=RENDER_UTILS_BUFFER_SSSC_PARAMS) uniform subsurfaceScatteringParametersBuffer { +LAYOUT(binding=RENDER_UTILS_BUFFER_SSSC_PARAMS) uniform subsurfaceScatteringParametersBuffer { ScatteringParameters parameters; }; diff --git a/libraries/render-utils/src/WorkloadResource.slh b/libraries/render-utils/src/WorkloadResource.slh index 81b6ed78ce..ed23abd5ae 100644 --- a/libraries/render-utils/src/WorkloadResource.slh +++ b/libraries/render-utils/src/WorkloadResource.slh @@ -25,8 +25,8 @@ struct WorkloadProxy { vec4 region; }; -#if defined(GPU_GL410) -layout(binding=0) uniform samplerBuffer workloadProxiesBuffer; +#if !defined(GPU_SSBO_TRANSFORM_OBJECT) +LAYOUT(binding=0) uniform samplerBuffer workloadProxiesBuffer; WorkloadProxy getWorkloadProxy(int i) { int offset = 2 * i; WorkloadProxy proxy; @@ -35,7 +35,7 @@ WorkloadProxy getWorkloadProxy(int i) { return proxy; } #else -layout(std140, binding=0) buffer workloadProxiesBuffer { +LAYOUT_STD140(binding=0) buffer workloadProxiesBuffer { WorkloadProxy _proxies[]; }; WorkloadProxy getWorkloadProxy(int i) { @@ -57,17 +57,23 @@ struct WorkloadView { vec4 regions[3]; }; -#if defined(GPU_GL410) -layout(binding=1) uniform samplerBuffer workloadViewsBuffer; +#if !defined(GPU_SSBO_TRANSFORM_OBJECT) +LAYOUT(binding=1) uniform samplerBuffer workloadViewsBuffer; WorkloadView getWorkloadView(int i) { - int offset = 2 * i; + int offset = 8 * i; WorkloadView view; - view.origin = texelFetch(workloadViewsBuffer, offset); - view.radiuses = texelFetch(workloadViewsBuffer, offset + 1); + view.direction_far = texelFetch(workloadViewsBuffer, offset + 0); + view.fov = texelFetch(workloadViewsBuffer, offset + 1); + view.origin = texelFetch(workloadViewsBuffer, offset + 2); + view.backFront[0] = texelFetch(workloadViewsBuffer, offset + 3); + view.backFront[1] = texelFetch(workloadViewsBuffer, offset + 4); + view.regions[0] = texelFetch(workloadViewsBuffer, offset + 5); + view.regions[1] = texelFetch(workloadViewsBuffer, offset + 6); + view.regions[2] = texelFetch(workloadViewsBuffer, offset + 7); return view; } #else -layout(std140, binding=1) buffer workloadViewsBuffer { +LAYOUT_STD140(binding=1) buffer workloadViewsBuffer { WorkloadView _views[]; }; WorkloadView getWorkloadView(int i) { diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index 013640d910..c6e3c49e54 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -16,8 +16,8 @@ <@include gpu/Color.slh@> <$declareColorWheel()$> -layout(binding=RENDER_UTILS_DEBUG_TEXTURE0) uniform sampler2D debugTexture0; -layout(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowMaps; +LAYOUT(binding=RENDER_UTILS_DEBUG_TEXTURE0) uniform sampler2D debugTexture0; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowMaps; <@include ShadowCore.slh@> @@ -36,7 +36,13 @@ float curvatureAO(float k) { layout(location=0) in vec2 uv; layout(location=0) out vec4 outFragColor; -//SOURCE_PLACEHOLDER +//SOURCE_PLACEHOLDER_BEGIN +vec4 getFragmentColor() { + DeferredFragment frag = unpackDeferredFragmentNoPosition(uv); + return vec4(pow(frag.albedo, vec3(1.0 / 2.2)), 1.0); +} +//SOURCE_PLACEHOLDER_END + void main(void) { outFragColor = getFragmentColor(); diff --git a/libraries/render-utils/src/deferred_light_point.slv b/libraries/render-utils/src/deferred_light_point.slv index 1f4c66b6e5..3e6329be83 100644 --- a/libraries/render-utils/src/deferred_light_point.slv +++ b/libraries/render-utils/src/deferred_light_point.slv @@ -22,7 +22,7 @@ <$declareLightBuffer(256)$> -layout(binding=RENDER_UTILS_BUFFER_LIGHT_INDEX) uniform lightIndexBuffer { +LAYOUT(binding=RENDER_UTILS_BUFFER_LIGHT_INDEX) uniform lightIndexBuffer { int lightIndex[256]; }; diff --git a/libraries/render-utils/src/deferred_light_spot.slv b/libraries/render-utils/src/deferred_light_spot.slv index c86551936b..0370acc6bc 100644 --- a/libraries/render-utils/src/deferred_light_spot.slv +++ b/libraries/render-utils/src/deferred_light_spot.slv @@ -21,7 +21,7 @@ <$declareLightBuffer(256)$> -layout(binding=RENDER_UTILS_BUFFER_LIGHT_INDEX) uniform lightIndexBuffer { +LAYOUT(binding=RENDER_UTILS_BUFFER_LIGHT_INDEX) uniform lightIndexBuffer { int lightIndex[256]; }; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord0; diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv index db4a33c450..2fdf3d773e 100644 --- a/libraries/render-utils/src/drawWorkloadView.slv +++ b/libraries/render-utils/src/drawWorkloadView.slv @@ -32,7 +32,7 @@ struct DrawMesh { vec4 verts[NUM_SEGMENT_PER_VIEW_REGION]; }; -layout(std140, binding=0) uniform DrawMeshBuffer { +LAYOUT_STD140(binding=0) uniform DrawMeshBuffer { DrawMesh _drawMeshBuffer; }; diff --git a/libraries/render-utils/src/forward_simple.slf b/libraries/render-utils/src/forward_simple.slf index ca3a13c024..9c86f9dff1 100644 --- a/libraries/render-utils/src/forward_simple.slf +++ b/libraries/render-utils/src/forward_simple.slf @@ -14,8 +14,10 @@ <@include DefaultMaterials.slh@> <@include ForwardGlobalLight.slh@> -<$declareEvalSkyboxGlobalColor()$> +<@include gpu/Transform.slh@> +<$declareEvalSkyboxGlobalColor()$> +<$declareStandardCameraTransform()$> // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; @@ -35,12 +37,6 @@ layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; layout(location=0) out vec4 _fragColor0; -<@include procedural/ProceduralCommon.slh@> - -#line 1001 -//PROCEDURAL_BLOCK - -#line 2030 void main(void) { vec3 normal = normalize(_normalWS.xyz); vec3 diffuse = _color.rgb; @@ -48,45 +44,18 @@ void main(void) { float shininess = DEFAULT_SHININESS; float emissiveAmount = 0.0; -#ifdef PROCEDURAL - -#ifdef PROCEDURAL_V1 - diffuse = getProceduralColor().rgb; - // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //diffuse = pow(diffuse, vec3(2.2)); - emissiveAmount = 1.0; -#else - emissiveAmount = getProceduralColors(diffuse, specular, shininess); -#endif - -#endif - TransformCamera cam = getTransformCamera(); vec3 fragPosition = _positionES.xyz; - if (emissiveAmount > 0.0) { - _fragColor0 = vec4(evalSkyboxGlobalColor( - cam._viewInverse, - 1.0, - DEFAULT_OCCLUSION, - fragPosition, - normal, - diffuse, - specular, - DEFAULT_METALLIC, - max(0.0, 1.0 - shininess / 128.0)), - 1.0); - } else { - _fragColor0 = vec4(evalSkyboxGlobalColor( - cam._viewInverse, - 1.0, - DEFAULT_OCCLUSION, - fragPosition, - normal, - diffuse, - DEFAULT_FRESNEL, - length(specular), - max(0.0, 1.0 - shininess / 128.0)), - 1.0); - } + _fragColor0 = vec4(evalSkyboxGlobalColor( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, + normal, + diffuse, + DEFAULT_FRESNEL, + length(specular), + max(0.0, 1.0 - shininess / 128.0)), + 1.0); } diff --git a/libraries/render-utils/src/forward_simple_textured.slf b/libraries/render-utils/src/forward_simple_textured.slf index 8570ae6183..ca31550b40 100644 --- a/libraries/render-utils/src/forward_simple_textured.slf +++ b/libraries/render-utils/src/forward_simple_textured.slf @@ -22,7 +22,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/forward_simple_textured_transparent.slf b/libraries/render-utils/src/forward_simple_textured_transparent.slf index 11c44c18a2..11d51bbd78 100644 --- a/libraries/render-utils/src/forward_simple_textured_transparent.slf +++ b/libraries/render-utils/src/forward_simple_textured_transparent.slf @@ -22,7 +22,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/forward_simple_textured_unlit.slf b/libraries/render-utils/src/forward_simple_textured_unlit.slf index 8ca46da499..ddbc5ae4d7 100644 --- a/libraries/render-utils/src/forward_simple_textured_unlit.slf +++ b/libraries/render-utils/src/forward_simple_textured_unlit.slf @@ -20,7 +20,7 @@ layout(location=0) out vec4 _fragColor0; // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; diff --git a/libraries/render-utils/src/fxaa.slf b/libraries/render-utils/src/fxaa.slf index f1096a3054..1539a87550 100644 --- a/libraries/render-utils/src/fxaa.slf +++ b/libraries/render-utils/src/fxaa.slf @@ -22,7 +22,7 @@ precision mediump float; precision mediump int; #endif -layout(binding=0) uniform sampler2D colorTexture; +LAYOUT(binding=0) uniform sampler2D colorTexture; //uniform sampler2D historyTexture; // FIXME make into a uniform buffer or push constant if this shader ever comes into use diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index c051801659..c22982bc3f 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -17,13 +17,13 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; -layout(binding=0) uniform sampler2D colorTexture; +LAYOUT(binding=0) uniform sampler2D colorTexture; struct FxaaBlendParams { vec4 sharpenIntensity; }; -layout(binding=0) uniform fxaaBlendParamsBuffer { +LAYOUT(binding=0) uniform fxaaBlendParamsBuffer { FxaaBlendParams params; }; diff --git a/libraries/render-utils/src/glowLine.slv b/libraries/render-utils/src/glowLine.slv index 075b291589..167aeb8c9e 100644 --- a/libraries/render-utils/src/glowLine.slv +++ b/libraries/render-utils/src/glowLine.slv @@ -21,7 +21,7 @@ struct LineData { float width; }; -layout(std140, binding=0) uniform LineDataBuffer { +LAYOUT_STD140(binding=0) uniform LineDataBuffer { LineData _lineData; }; diff --git a/libraries/render-utils/src/grid.slf b/libraries/render-utils/src/grid.slf index c2380c980d..8e9b35dace 100644 --- a/libraries/render-utils/src/grid.slf +++ b/libraries/render-utils/src/grid.slf @@ -20,7 +20,7 @@ struct Grid { vec4 edge; }; -layout(binding=0) uniform gridBuffer { +LAYOUT(binding=0) uniform gridBuffer { Grid grid; }; diff --git a/libraries/render-utils/src/hmd_ui.slf b/libraries/render-utils/src/hmd_ui.slf index eebeb2e060..6895a90f9e 100644 --- a/libraries/render-utils/src/hmd_ui.slf +++ b/libraries/render-utils/src/hmd_ui.slf @@ -13,13 +13,13 @@ // <@include render-utils/ShaderConstants.h@> -layout(binding=0) uniform sampler2D hudTexture; +LAYOUT(binding=0) uniform sampler2D hudTexture; struct HUDData { float alpha; }; -layout(std140, binding=0) uniform hudBuffer { +LAYOUT_STD140(binding=0) uniform hudBuffer { HUDData hud; }; diff --git a/libraries/render-utils/src/hmd_ui.slv b/libraries/render-utils/src/hmd_ui.slv index ab0d77c42a..6e782d1672 100644 --- a/libraries/render-utils/src/hmd_ui.slv +++ b/libraries/render-utils/src/hmd_ui.slv @@ -22,7 +22,7 @@ struct HUDData { float alpha; }; -layout(std140, binding=0) uniform hudBuffer { +LAYOUT_STD140(binding=0) uniform hudBuffer { HUDData hud; }; diff --git a/libraries/render-utils/src/parabola.slv b/libraries/render-utils/src/parabola.slv index 31b3ab8fae..53dfc75cfe 100644 --- a/libraries/render-utils/src/parabola.slv +++ b/libraries/render-utils/src/parabola.slv @@ -22,7 +22,7 @@ struct ParabolaData { ivec3 spare; }; -layout(std140, binding=0) uniform parabolaData { +LAYOUT_STD140(binding=0) uniform parabolaData { ParabolaData _parabolaData; }; diff --git a/libraries/render-utils/src/render-utils/ShaderConstants.h b/libraries/render-utils/src/render-utils/ShaderConstants.h index ccf6314a39..2d777d502f 100644 --- a/libraries/render-utils/src/render-utils/ShaderConstants.h +++ b/libraries/render-utils/src/render-utils/ShaderConstants.h @@ -131,15 +131,6 @@ namespace render_utils { namespace slot { -namespace uniform { -enum Uniform { - TextColor = RENDER_UTILS_UNIFORM_TEXT_COLOR, - TextOutline = RENDER_UTILS_UNIFORM_TEXT_OUTLINE, - TaaSharpenIntensity = GPU_UNIFORM_EXTRA0, - HighlightOutlineWidth = GPU_UNIFORM_EXTRA0, -}; -} - namespace buffer { enum Buffer { DeferredFrameTransform = RENDER_UTILS_BUFFER_DEFERRED_FRAME_TRANSFORM, diff --git a/libraries/render-utils/src/render-utils/debug_deferred_buffer.slp b/libraries/render-utils/src/render-utils/debug_deferred_buffer.slp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/render-utils/src/render-utils/simple.slp b/libraries/render-utils/src/render-utils/simple.slp index 8a6e2e4f99..e69de29bb2 100644 --- a/libraries/render-utils/src/render-utils/simple.slp +++ b/libraries/render-utils/src/render-utils/simple.slp @@ -1 +0,0 @@ -FRAGMENT forward_simple_textured diff --git a/libraries/render-utils/src/render-utils/simpleTranslucent.slp b/libraries/render-utils/src/render-utils/simpleTranslucent.slp deleted file mode 100644 index 0163b09b84..0000000000 --- a/libraries/render-utils/src/render-utils/simpleTranslucent.slp +++ /dev/null @@ -1,2 +0,0 @@ -VERTEX simple -FRAGMENT forward_simple_textured_transparent diff --git a/libraries/render-utils/src/render-utils/simpleTranslucentUnlit.slp b/libraries/render-utils/src/render-utils/simpleTranslucentUnlit.slp deleted file mode 100644 index f1d1ec39be..0000000000 --- a/libraries/render-utils/src/render-utils/simpleTranslucentUnlit.slp +++ /dev/null @@ -1,2 +0,0 @@ -VERTEX simple -FRAGMENT simple_transparent_textured_unlit diff --git a/libraries/render-utils/src/render-utils/simpleUnlit.slp b/libraries/render-utils/src/render-utils/simpleUnlit.slp deleted file mode 100644 index ab491aa290..0000000000 --- a/libraries/render-utils/src/render-utils/simpleUnlit.slp +++ /dev/null @@ -1,2 +0,0 @@ -VERTEX simple -FRAGMENT forward_simple_textured_unlit diff --git a/libraries/render-utils/src/render-utils/simple_transparent.slp b/libraries/render-utils/src/render-utils/simple_transparent.slp new file mode 100644 index 0000000000..10e6b388c4 --- /dev/null +++ b/libraries/render-utils/src/render-utils/simple_transparent.slp @@ -0,0 +1 @@ +VERTEX simple diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index d35396e469..35e670eef8 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -13,14 +13,14 @@ <@include DeferredBufferWrite.slh@> <@include render-utils/ShaderConstants.h@> -layout(binding=0) uniform sampler2D Font; +LAYOUT(binding=0) uniform sampler2D Font; struct TextParams { vec4 color; vec4 outline; }; -layout(binding=0) uniform textParamsBuffer { +LAYOUT(binding=0) uniform textParamsBuffer { TextParams params; }; diff --git a/libraries/render-utils/src/sdf_text3D_transparent.slf b/libraries/render-utils/src/sdf_text3D_transparent.slf index 9dffca2038..6e271e1463 100644 --- a/libraries/render-utils/src/sdf_text3D_transparent.slf +++ b/libraries/render-utils/src/sdf_text3D_transparent.slf @@ -13,14 +13,14 @@ <@include DeferredBufferWrite.slh@> <@include render-utils/ShaderConstants.h@> -layout(binding=0) uniform sampler2D Font; +LAYOUT(binding=0) uniform sampler2D Font; struct TextParams { vec4 color; vec4 outline; }; -layout(binding=0) uniform textParamsBuffer { +LAYOUT(binding=0) uniform textParamsBuffer { TextParams params; }; diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index a7f5151880..039dbc4278 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -35,7 +35,17 @@ layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; <@include procedural/ProceduralCommon.slh@> #line 1001 -//PROCEDURAL_BLOCK +//PROCEDURAL_BLOCK_BEGIN + +vec3 getProceduralColor() { + return _color.rgb; +} + +float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) { + return 1.0; +} + +//PROCEDURAL_BLOCK_END #line 2030 void main(void) { diff --git a/libraries/render-utils/src/simple_fade.slf b/libraries/render-utils/src/simple_fade.slf deleted file mode 100644 index 97ed0c570c..0000000000 --- a/libraries/render-utils/src/simple_fade.slf +++ /dev/null @@ -1,110 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// simple_fade.frag -// fragment shader -// -// Created by Olivier Prat on 06/05/17. -// Copyright 2017 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 DeferredBufferWrite.slh@> - -<@include Fade.slh@> -<$declareFadeFragmentInstanced()$> - -<@include render-utils/ShaderConstants.h@> - -// the interpolated normal -layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; -layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normalMS; -layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; -layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; -#define _texCoord0 _texCoord01.xy -#define _texCoord1 _texCoord01.zw -layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _positionMS; -layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; -layout(location=RENDER_UTILS_ATTR_POSITION_WS) in vec4 _positionWS; - -// For retro-compatibility -#define _normal _normalWS -#define _modelNormal _normalMS -#define _position _positionMS -#define _eyePosition _positionES - -<@include procedural/ProceduralCommon.slh@> - -#line 1001 -//PROCEDURAL_BLOCK - -#line 2030 -void main(void) { - vec3 fadeEmissive; - FadeObjectParams fadeParams; - - <$fetchFadeObjectParamsInstanced(fadeParams)$> - applyFade(fadeParams, _positionWS.xyz, fadeEmissive); - - vec3 normal = normalize(_normalWS.xyz); - vec3 diffuse = _color.rgb; - vec3 specular = DEFAULT_SPECULAR; - float shininess = DEFAULT_SHININESS; - float emissiveAmount = 0.0; - -#ifdef PROCEDURAL - -#ifdef PROCEDURAL_V1 - specular = getProceduralColor().rgb; - // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //specular = pow(specular, vec3(2.2)); - emissiveAmount = 1.0; -#else - emissiveAmount = getProceduralColors(diffuse, specular, shininess); -#endif - -#endif - - const float ALPHA_THRESHOLD = 0.999; - if (_color.a < ALPHA_THRESHOLD) { - if (emissiveAmount > 0.0) { - packDeferredFragmentTranslucent( - normal, - _color.a, - specular+fadeEmissive, - DEFAULT_FRESNEL, - DEFAULT_ROUGHNESS); - } else { - packDeferredFragmentTranslucent( - normal, - _color.a, - diffuse+fadeEmissive, - DEFAULT_FRESNEL, - DEFAULT_ROUGHNESS); - } - } else { - if (emissiveAmount > 0.0) { - packDeferredFragmentLightmap( - normal, - 1.0, - diffuse+fadeEmissive, - max(0.0, 1.0 - shininess / 128.0), - DEFAULT_METALLIC, - specular, - specular); - } else { - packDeferredFragment( - normal, - 1.0, - diffuse, - max(0.0, 1.0 - shininess / 128.0), - length(specular), - DEFAULT_EMISSIVE+fadeEmissive, - DEFAULT_OCCLUSION, - DEFAULT_SCATTERING); - } - } -} diff --git a/libraries/render-utils/src/simple_opaque_web_browser.slf b/libraries/render-utils/src/simple_opaque_web_browser.slf index cf4828d3b3..36b0c825ad 100644 --- a/libraries/render-utils/src/simple_opaque_web_browser.slf +++ b/libraries/render-utils/src/simple_opaque_web_browser.slf @@ -18,7 +18,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/simple_textured.slf b/libraries/render-utils/src/simple_textured.slf index 7676844084..b308b57345 100644 --- a/libraries/render-utils/src/simple_textured.slf +++ b/libraries/render-utils/src/simple_textured.slf @@ -17,7 +17,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/simple_textured_fade.slf b/libraries/render-utils/src/simple_textured_fade.slf index 600f19be0f..ad2b636708 100644 --- a/libraries/render-utils/src/simple_textured_fade.slf +++ b/libraries/render-utils/src/simple_textured_fade.slf @@ -20,7 +20,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/simple_textured_unlit.slf b/libraries/render-utils/src/simple_textured_unlit.slf index e3d9b9daf6..f33cb704dc 100644 --- a/libraries/render-utils/src/simple_textured_unlit.slf +++ b/libraries/render-utils/src/simple_textured_unlit.slf @@ -18,7 +18,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/simple_textured_unlit_fade.slf b/libraries/render-utils/src/simple_textured_unlit_fade.slf index bffadbe819..494920b363 100644 --- a/libraries/render-utils/src/simple_textured_unlit_fade.slf +++ b/libraries/render-utils/src/simple_textured_unlit_fade.slf @@ -20,7 +20,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/simple_transparent.slf b/libraries/render-utils/src/simple_transparent.slf index 5db54aa770..0e29ed7470 100644 --- a/libraries/render-utils/src/simple_transparent.slf +++ b/libraries/render-utils/src/simple_transparent.slf @@ -39,7 +39,18 @@ layout(location=0) out vec4 _fragColor0; <@include procedural/ProceduralCommon.slh@> #line 1001 -//PROCEDURAL_BLOCK + +//PROCEDURAL_BLOCK_BEGIN + +vec3 getProceduralColor() { + return _color.rgb; +} + +float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) { + return 1.0; +} + +//PROCEDURAL_BLOCK_END #line 2030 void main(void) { diff --git a/libraries/render-utils/src/simple_transparent_textured.slf b/libraries/render-utils/src/simple_transparent_textured.slf index 5573a7aa22..ef83914096 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(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/simple_transparent_textured_fade.slf b/libraries/render-utils/src/simple_transparent_textured_fade.slf index 44a3fe2e01..5fac67e1d2 100644 --- a/libraries/render-utils/src/simple_transparent_textured_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_fade.slf @@ -26,7 +26,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/simple_transparent_textured_unlit.slf b/libraries/render-utils/src/simple_transparent_textured_unlit.slf index 9d43e41c2f..bf3dbbdf88 100644 --- a/libraries/render-utils/src/simple_transparent_textured_unlit.slf +++ b/libraries/render-utils/src/simple_transparent_textured_unlit.slf @@ -17,7 +17,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; diff --git a/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf index 43c28c41c3..943f361ead 100644 --- a/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf +++ b/libraries/render-utils/src/simple_transparent_textured_unlit_fade.slf @@ -19,7 +19,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; diff --git a/libraries/render-utils/src/simple_transparent_web_browser.slf b/libraries/render-utils/src/simple_transparent_web_browser.slf index df92d238bf..2adc16e278 100644 --- a/libraries/render-utils/src/simple_transparent_web_browser.slf +++ b/libraries/render-utils/src/simple_transparent_web_browser.slf @@ -18,7 +18,7 @@ <@include render-utils/ShaderConstants.h@> // the albedo texture -layout(binding=0) uniform sampler2D originalTexture; +LAYOUT(binding=0) uniform sampler2D originalTexture; // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; diff --git a/libraries/render-utils/src/ssao.slh b/libraries/render-utils/src/ssao.slh index b149d8f912..f0d522a41c 100644 --- a/libraries/render-utils/src/ssao.slh +++ b/libraries/render-utils/src/ssao.slh @@ -44,7 +44,7 @@ struct AmbientOcclusionParams { float _gaussianCoefs[8]; }; -layout(binding=RENDER_UTILS_BUFFER_SSAO_PARAMS) uniform ambientOcclusionParamsBuffer { +LAYOUT(binding=RENDER_UTILS_BUFFER_SSAO_PARAMS) uniform ambientOcclusionParamsBuffer { AmbientOcclusionParams params; }; @@ -232,7 +232,7 @@ vec3 getTapLocationClamped(int sampleNumber, float spinAngle, float outerRadius, // the depth pyramid texture -layout(binding=RENDER_UTILS_TEXTURE_SSAO_PYRAMID) uniform sampler2D pyramidMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SSAO_PYRAMID) uniform sampler2D pyramidMap; float getZEye(ivec2 pixel, int level) { return -texelFetch(pyramidMap, pixel, level).x; @@ -313,7 +313,7 @@ float evalAO(in vec3 C, in vec3 n_C, in vec3 Q) { <$declareAmbientOcclusion()$> // the source occlusion texture -layout(binding=RENDER_UTILS_TEXTURE_SSAO_OCCLUSION) uniform sampler2D occlusionMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SSAO_OCCLUSION) uniform sampler2D occlusionMap; vec2 fetchOcclusionDepthRaw(ivec2 coords, out vec3 raw) { raw = texelFetch(occlusionMap, coords, 0).xyz; diff --git a/libraries/render-utils/src/ssao_debugOcclusion.slf b/libraries/render-utils/src/ssao_debugOcclusion.slf index ab7989e35e..e15e52f448 100644 --- a/libraries/render-utils/src/ssao_debugOcclusion.slf +++ b/libraries/render-utils/src/ssao_debugOcclusion.slf @@ -26,7 +26,7 @@ struct DebugParams{ vec4 pixelInfo; }; -layout(binding=RENDER_UTILS_BUFFER_SSAO_DEBUG_PARAMS) uniform debugAmbientOcclusionBuffer { +LAYOUT(binding=RENDER_UTILS_BUFFER_SSAO_DEBUG_PARAMS) uniform debugAmbientOcclusionBuffer { DebugParams debugParams; }; diff --git a/libraries/render-utils/src/ssao_makePyramid.slf b/libraries/render-utils/src/ssao_makePyramid.slf index c87fe1e682..eae1b853f9 100644 --- a/libraries/render-utils/src/ssao_makePyramid.slf +++ b/libraries/render-utils/src/ssao_makePyramid.slf @@ -14,7 +14,7 @@ <@include ssao.slh@> <$declareAmbientOcclusion()$> -layout(binding=0) uniform sampler2D depthMap; +LAYOUT(binding=0) uniform sampler2D depthMap; layout(location=0) out vec4 outFragColor; diff --git a/libraries/render-utils/src/standardDrawTexture.slf b/libraries/render-utils/src/standardDrawTexture.slf index 1a8af0f71c..620c811f75 100644 --- a/libraries/render-utils/src/standardDrawTexture.slf +++ b/libraries/render-utils/src/standardDrawTexture.slf @@ -14,7 +14,7 @@ <@include gpu/ShaderConstants.h@> // the texture -layout(binding=0) uniform sampler2D colorMap; +LAYOUT(binding=0) uniform sampler2D colorMap; layout(location=GPU_ATTR_POSITION) in vec3 varPosition; layout(location=GPU_ATTR_NORMAL) in vec3 varNormal; diff --git a/libraries/render-utils/src/standardDrawTextureNoBlend.slf b/libraries/render-utils/src/standardDrawTextureNoBlend.slf index 95138d123f..83915cd856 100644 --- a/libraries/render-utils/src/standardDrawTextureNoBlend.slf +++ b/libraries/render-utils/src/standardDrawTextureNoBlend.slf @@ -14,7 +14,7 @@ <@include gpu/ShaderConstants.h@> // the texture -layout(binding=0) uniform sampler2D colorMap; +LAYOUT(binding=0) uniform sampler2D colorMap; layout(location=GPU_ATTR_POSITION) in vec3 varPosition; layout(location=GPU_ATTR_NORMAL) in vec3 varNormal; diff --git a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf index 8664fa16fd..877c31c23d 100644 --- a/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf +++ b/libraries/render-utils/src/subsurfaceScattering_drawScattering.slf @@ -26,10 +26,14 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 _fragColor; -// FIXME make into a uniform buffer or push constant if this shader ever comes into use -vec2 uniformCursorTexcoord = vec2(0.5); +struct SSSDebugParams { + vec4 cursorTexCoordSpare2; +}; -//uniform vec3 uniformLightVector = vec3(1.0); +// Deferred frame transform uses slot 0 +LAYOUT_STD140(binding=1) uniform sssDebugParamsBuffer { + SSSDebugParams sssDebugParams; +}; vec3 evalScatteringBRDF(vec2 texcoord) { DeferredFragment fragment = unpackDeferredFragmentNoPosition(texcoord); @@ -76,8 +80,6 @@ vec3 drawScatteringTableUV(vec2 cursor, vec2 texcoord) { vec3 bentNdotL = evalScatteringBentNdotL(normal, midNormal, lowNormal, fragLightDir); - // return clamp(bentNdotL * 0.5 + 0.5, 0.0, 1.0); - 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.0 * curvature, 0.0, 1.0)); @@ -104,10 +106,8 @@ vec3 drawScatteringTableUV(vec2 cursor, vec2 texcoord) { } void main(void) { - // _fragColor = vec4(evalScatteringBRDF(varTexCoord0), 1.0); - // _fragColor = vec4(uniformCursorTexcoord, 0.0, 1.0); - - _fragColor = vec4(drawScatteringTableUV(uniformCursorTexcoord, varTexCoord0), 1.0); + vec2 cursorTexcoord = sssDebugParams.cursorTexCoordSpare2.xy; + _fragColor = vec4(drawScatteringTableUV(cursorTexcoord, varTexCoord0), 1.0); } diff --git a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf index f018ee1105..efff6e913c 100644 --- a/libraries/render-utils/src/surfaceGeometry_copyDepth.slf +++ b/libraries/render-utils/src/surfaceGeometry_copyDepth.slf @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -layout(binding=0) uniform sampler2D depthMap; +LAYOUT(binding=0) uniform sampler2D depthMap; layout(location=0) out vec4 outFragColor; diff --git a/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf b/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf index 34e78ea4ff..e4020dbdec 100644 --- a/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf +++ b/libraries/render-utils/src/surfaceGeometry_downsampleDepthNormal.slf @@ -15,8 +15,8 @@ <@include gpu/PackedNormal.slh@> <@include render-utils/ShaderConstants.h@> -layout(binding=RENDER_UTILS_TEXTURE_SG_DEPTH) uniform sampler2D linearDepthMap; -layout(binding=RENDER_UTILS_TEXTURE_SG_NORMAL) uniform sampler2D normalMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SG_DEPTH) uniform sampler2D linearDepthMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SG_NORMAL) uniform sampler2D normalMap; layout(location=0) in vec2 varTexCoord0; diff --git a/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf index b49bd618da..363fd0d4f8 100644 --- a/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf +++ b/libraries/render-utils/src/surfaceGeometry_makeCurvature.slf @@ -25,7 +25,7 @@ struct SurfaceGeometryParams { vec4 curvatureInfo; }; -layout(binding= RENDER_UTILS_BUFFER_SG_PARAMS) uniform surfaceGeometryParamsBuffer { +LAYOUT(binding= RENDER_UTILS_BUFFER_SG_PARAMS) uniform surfaceGeometryParamsBuffer { SurfaceGeometryParams params; }; @@ -46,7 +46,7 @@ bool isFullResolution() { } -layout(binding=RENDER_UTILS_TEXTURE_SG_DEPTH) uniform sampler2D linearDepthMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SG_DEPTH) uniform sampler2D linearDepthMap; float getZEye(ivec2 pixel) { return -texelFetch(linearDepthMap, pixel, 0).x; @@ -59,7 +59,7 @@ vec2 sideToFrameTexcoord(vec2 side, vec2 texcoordPos) { return vec2((texcoordPos.x + side.x) * side.y, texcoordPos.y); } -layout(binding=RENDER_UTILS_TEXTURE_SG_NORMAL) uniform sampler2D normalMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SG_NORMAL) uniform sampler2D normalMap; vec3 getRawNormal(vec2 texcoord) { return texture(normalMap, texcoord).xyz; diff --git a/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf b/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf index 116f3b7686..fe0c320d1b 100644 --- a/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf +++ b/libraries/render-utils/src/surfaceGeometry_makeLinearDepth.slf @@ -16,7 +16,7 @@ <@include DeferredTransform.slh@> <$declareDeferredFrameTransform()$> -layout(binding=RENDER_UTILS_TEXTURE_SG_DEPTH) uniform sampler2D depthMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SG_DEPTH) uniform sampler2D depthMap; layout(location=0) out vec4 outFragColor; diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh index 2161ad9524..784c0824d5 100644 --- a/libraries/render-utils/src/taa.slh +++ b/libraries/render-utils/src/taa.slh @@ -16,11 +16,11 @@ <@include render-utils/ShaderConstants.h@> <@include gpu/Color.slh@> -layout(binding=RENDER_UTILS_TEXTURE_TAA_HISTORY) uniform sampler2D historyMap; -layout(binding=RENDER_UTILS_TEXTURE_TAA_SOURCE) uniform sampler2D sourceMap; -layout(binding=RENDER_UTILS_TEXTURE_TAA_VELOCITY) uniform sampler2D velocityMap; -layout(binding=RENDER_UTILS_TEXTURE_TAA_DEPTH) uniform sampler2D depthMap; -layout(binding=RENDER_UTILS_TEXTURE_TAA_NEXT) uniform sampler2D nextMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_HISTORY) uniform sampler2D historyMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_SOURCE) uniform sampler2D sourceMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_VELOCITY) uniform sampler2D velocityMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_DEPTH) uniform sampler2D depthMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_NEXT) uniform sampler2D nextMap; struct TAAParams { @@ -33,7 +33,7 @@ struct TAAParams vec4 regionInfo; }; -layout(std140, binding=RENDER_UTILS_BUFFER_TAA_PARAMS) uniform taaParamsBuffer { +LAYOUT_STD140(binding=RENDER_UTILS_BUFFER_TAA_PARAMS) uniform taaParamsBuffer { TAAParams params; }; diff --git a/libraries/render-utils/src/toneMapping.slf b/libraries/render-utils/src/toneMapping.slf index 8d89e54a1b..29f618c2f0 100644 --- a/libraries/render-utils/src/toneMapping.slf +++ b/libraries/render-utils/src/toneMapping.slf @@ -26,7 +26,7 @@ const int ToneCurveGamma22 = 1; const int ToneCurveReinhard = 2; const int ToneCurveFilmic = 3; -layout(binding=RENDER_UTILS_BUFFER_TM_PARAMS) uniform toneMappingParamsBuffer { +LAYOUT(binding=RENDER_UTILS_BUFFER_TM_PARAMS) uniform toneMappingParamsBuffer { ToneMappingParams params; }; float getTwoPowExposure() { @@ -36,7 +36,7 @@ int getToneCurve() { return params._toneCurve_s0_s1_s2.x; } -layout(binding=RENDER_UTILS_TEXTURE_TM_COLOR) uniform sampler2D colorMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_TM_COLOR) uniform sampler2D colorMap; layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; diff --git a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf index 083440dbf8..0ec63a7b1d 100644 --- a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf +++ b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf @@ -17,7 +17,7 @@ layout(location=0) in vec2 varTexCoord0; layout(location=0) out vec4 outFragColor; -layout(binding=RENDER_UTILS_TEXTURE_TAA_DEPTH) uniform sampler2D depthMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_TAA_DEPTH) uniform sampler2D depthMap; void main(void) { diff --git a/libraries/render-utils/src/zone_drawSkybox.slf b/libraries/render-utils/src/zone_drawSkybox.slf index 77de75a305..f8d1326b3a 100644 --- a/libraries/render-utils/src/zone_drawSkybox.slf +++ b/libraries/render-utils/src/zone_drawSkybox.slf @@ -12,13 +12,13 @@ <@include render-utils/ShaderConstants.h@> // FIXME use declareSkyboxMap from LightAmbient.slh? -layout(binding=RENDER_UTILS_TEXTURE_SKYBOX) uniform samplerCube skyboxMap; +LAYOUT(binding=RENDER_UTILS_TEXTURE_SKYBOX) uniform samplerCube skyboxMap; struct Skybox { vec4 color; }; -layout(binding=RENDER_UTILS_BUFFER_DEBUG_SKYBOX) uniform skyboxBuffer { +LAYOUT(binding=RENDER_UTILS_BUFFER_DEBUG_SKYBOX) uniform skyboxBuffer { Skybox skybox; }; diff --git a/libraries/render/src/render/BlurTask.slh b/libraries/render/src/render/BlurTask.slh index c07e71688a..db6b8e3bab 100644 --- a/libraries/render/src/render/BlurTask.slh +++ b/libraries/render/src/render/BlurTask.slh @@ -21,7 +21,7 @@ struct BlurParameters { vec2 taps[BLUR_MAX_NUM_TAPS]; }; -layout(binding=0) uniform blurParamsBuffer { +LAYOUT(binding=0) uniform blurParamsBuffer { BlurParameters parameters; }; @@ -76,7 +76,7 @@ float getPosLinearDepthFar() { <$declareBlurUniforms()$> -layout(binding=0) uniform sampler2D sourceMap; +LAYOUT(binding=0) uniform sampler2D sourceMap; vec4 pixelShaderGaussian(vec2 texcoord, vec2 direction, vec2 pixelStep) { texcoord = evalTexcoordTransformed(texcoord); @@ -112,8 +112,8 @@ vec4 pixelShaderGaussian(vec2 texcoord, vec2 direction, vec2 pixelStep) { <$declareBlurUniforms()$> -layout(binding=0) uniform sampler2D sourceMap; -layout(binding=1) uniform sampler2D depthMap; +LAYOUT(binding=0) uniform sampler2D sourceMap; +LAYOUT(binding=1) uniform sampler2D depthMap; vec4 pixelShaderGaussianDepthAware(vec2 texcoord, vec2 direction, vec2 pixelStep) { texcoord = evalTexcoordTransformed(texcoord); diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 2b2accde37..d742428897 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -86,29 +86,30 @@ void ShapePlumber::addPipeline(const Key& key, const gpu::ShaderPointer& program void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& program, const gpu::StatePointer& state, BatchSetter batchSetter, ItemSetter itemSetter) { ShapeKey key{ filter._flags }; + const auto& reflection = program->getReflection(); auto locations = std::make_shared(); - locations->albedoTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialAlbedo); - locations->roughnessTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialRoughness); - locations->normalTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialNormal); - locations->metallicTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialMetallic); - locations->emissiveTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialEmissiveLightmap); - locations->occlusionTextureUnit = program->getTextures().isValid(graphics::slot::texture::MaterialOcclusion); - locations->lightingModelBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::LightModel); - locations->skinClusterBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::Skinning); - locations->materialBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::Material); - locations->texMapArrayBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::TexMapArray); - locations->keyLightBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::KeyLight); - locations->lightBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::Light); - locations->lightAmbientBufferUnit = program->getUniformBuffers().isValid(graphics::slot::buffer::AmbientLight); - locations->lightAmbientMapUnit = program->getTextures().isValid(graphics::slot::texture::Skybox); - locations->fadeMaskTextureUnit = program->getTextures().isValid(render_utils::slot::texture::FadeMask); - locations->fadeParameterBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::FadeParameters); - locations->fadeObjectParameterBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::FadeObjectParameters); - locations->hazeParameterBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::HazeParams); + locations->albedoTextureUnit = reflection.validTexture(graphics::slot::texture::MaterialAlbedo); + locations->roughnessTextureUnit = reflection.validTexture(graphics::slot::texture::MaterialRoughness); + locations->normalTextureUnit = reflection.validTexture(graphics::slot::texture::MaterialNormal); + locations->metallicTextureUnit = reflection.validTexture(graphics::slot::texture::MaterialMetallic); + locations->emissiveTextureUnit = reflection.validTexture(graphics::slot::texture::MaterialEmissiveLightmap); + locations->occlusionTextureUnit = reflection.validTexture(graphics::slot::texture::MaterialOcclusion); + locations->lightingModelBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::LightModel); + locations->skinClusterBufferUnit = reflection.validUniformBuffer(graphics::slot::buffer::Skinning); + locations->materialBufferUnit = reflection.validUniformBuffer(graphics::slot::buffer::Material); + locations->texMapArrayBufferUnit = reflection.validUniformBuffer(graphics::slot::buffer::TexMapArray); + locations->keyLightBufferUnit = reflection.validUniformBuffer(graphics::slot::buffer::KeyLight); + locations->lightBufferUnit = reflection.validUniformBuffer(graphics::slot::buffer::Light); + locations->lightAmbientBufferUnit = reflection.validUniformBuffer(graphics::slot::buffer::AmbientLight); + locations->lightAmbientMapUnit = reflection.validTexture(graphics::slot::texture::Skybox); + locations->fadeMaskTextureUnit = reflection.validTexture(render_utils::slot::texture::FadeMask); + locations->fadeParameterBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::FadeParameters); + locations->fadeObjectParameterBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::FadeObjectParameters); + locations->hazeParameterBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::HazeParams); if (key.isTranslucent()) { - locations->lightClusterGridBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::LightClusterGrid); - locations->lightClusterContentBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::LightClusterContent); - locations->lightClusterFrustumBufferUnit = program->getUniformBuffers().isValid(render_utils::slot::buffer::LightClusterFrustumGrid); + locations->lightClusterGridBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::LightClusterGrid); + locations->lightClusterContentBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::LightClusterContent); + locations->lightClusterFrustumBufferUnit = reflection.validUniformBuffer(render_utils::slot::buffer::LightClusterFrustumGrid); } { diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index bd6ac6521a..24c17d43f1 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -258,7 +258,7 @@ public: using ItemSetter = std::function; - ShapePipeline(gpu::PipelinePointer pipeline, LocationsPointer locations, BatchSetter batchSetter = nullptr, ItemSetter itemSetter = nullptr) : + ShapePipeline(const gpu::PipelinePointer& pipeline, const LocationsPointer& locations, const BatchSetter& batchSetter = nullptr, const ItemSetter& itemSetter = nullptr) : pipeline(pipeline), locations(locations), _batchSetter(batchSetter), diff --git a/libraries/render/src/render/drawItemBounds.slv b/libraries/render/src/render/drawItemBounds.slv index ea4d0f24e6..0a9615c9c2 100644 --- a/libraries/render/src/render/drawItemBounds.slv +++ b/libraries/render/src/render/drawItemBounds.slv @@ -24,7 +24,7 @@ struct DrawItemBoundsParams { vec4 color; }; -layout(binding=0) uniform drawItemBoundsParamsBuffer { +LAYOUT(binding=0) uniform drawItemBoundsParamsBuffer { DrawItemBoundsParams params; }; @@ -34,8 +34,8 @@ struct ItemBound { vec4 boundDim_s; }; -#if defined(GPU_GL410) -layout(binding=0) uniform samplerBuffer ssbo0Buffer; +#if !defined(GPU_SSBO_TRANSFORM_OBJECT) +LAYOUT(binding=0) uniform samplerBuffer ssbo0Buffer; ItemBound getItemBound(int i) { int offset = 2 * i; ItemBound bound; @@ -44,7 +44,7 @@ ItemBound getItemBound(int i) { return bound; } #else -layout(std140, binding=0) buffer ssbo0Buffer { +LAYOUT_STD140(binding=0) buffer ssbo0Buffer { ItemBound bounds[]; }; ItemBound getItemBound(int i) { diff --git a/libraries/render/src/render/drawItemStatus.slf b/libraries/render/src/render/drawItemStatus.slf index 9409ee6171..e88cf4c920 100644 --- a/libraries/render/src/render/drawItemStatus.slf +++ b/libraries/render/src/render/drawItemStatus.slf @@ -15,7 +15,7 @@ layout(location=0) in vec4 varColor; layout(location=1) in vec3 varTexcoord; layout(location=0) out vec4 outFragColor; -layout(binding=0) uniform sampler2D _icons; +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); diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 31436bbf8b..588377c072 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -17,6 +17,6 @@ if (NOT ANDROID) endif () -link_hifi_libraries(shared networking octree gpu procedural graphics model-networking ktx recording avatars fbx entities controllers animation audio physics image midi) +link_hifi_libraries(shared networking octree shaders gpu procedural graphics model-networking ktx recording avatars fbx entities controllers animation audio physics image midi) # ui includes gl, but link_hifi_libraries does not use transitive includes, so gl must be explicit include_hifi_library_headers(gl) diff --git a/libraries/shaders/CMakeLists.txt b/libraries/shaders/CMakeLists.txt index a065c635e7..1d9c4d59a4 100644 --- a/libraries/shaders/CMakeLists.txt +++ b/libraries/shaders/CMakeLists.txt @@ -1,16 +1,7 @@ set(TARGET_NAME shaders) autoscribe_shader_libs(gpu graphics display-plugins procedural render render-utils entities-renderer) setup_hifi_library(Gui) - -add_dependencies(${TARGET_NAME} compiled_shaders reflected_shaders) - -# Despite the dependency above, the autogen logic will attempt to compile the QRC before -# the compiled_shaders project is built causing an error on a clean workspace because the -# QRC references files generated by the compiled_shaders target -# To fix that we need to explicitly add every shader as a dependnecy of the autogen process -foreach(COMPILED_SHADER ${COMPILED_SHADERS}) - set_property(TARGET ${TARGET_NAME} APPEND PROPERTY AUTOGEN_TARGET_DEPENDS "${COMPILED_SHADER}") -endforeach() +add_dependencies(${TARGET_NAME} scribed_shaders spirv_shaders reflected_shaders) link_hifi_libraries(shared gl) - +target_json() diff --git a/libraries/shaders/ShaderEnums.cpp.in b/libraries/shaders/ShaderEnums.cpp.in index 7f4751f116..042362288d 100644 --- a/libraries/shaders/ShaderEnums.cpp.in +++ b/libraries/shaders/ShaderEnums.cpp.in @@ -1,11 +1,19 @@ #include "ShaderEnums.h" +#include namespace shader { - -uint32_t all_programs[] = { +const std::vector& allPrograms() { + static const std::vector ALL_PROGRAMS{{ @SHADER_PROGRAMS_ARRAY@ - (uint32_t)-1 -}; - + }}; + return ALL_PROGRAMS; } +const std::vector& allShaders() { + static const std::vector ALL_SHADERS{{ +@SHADER_SHADERS_ARRAY@ + }}; + return ALL_SHADERS; +} + +} \ No newline at end of file diff --git a/libraries/shaders/headers/310es/header.glsl b/libraries/shaders/headers/310es/header.glsl new file mode 100644 index 0000000000..ac48d5c94c --- /dev/null +++ b/libraries/shaders/headers/310es/header.glsl @@ -0,0 +1,15 @@ +#version 310 es +#define GPU_GLES +#define GPU_GLES_310 +#define BITFIELD highp int +#define LAYOUT(X) layout(X) +#define LAYOUT_STD140(X) layout(std140, X) +#ifdef VULKAN + #define gl_InstanceID gl_InstanceIndex + #define gl_VertexID gl_VertexIndex +#endif +#extension GL_EXT_texture_buffer : enable +precision highp float; +precision highp samplerBuffer; +precision highp sampler2DShadow; +precision highp sampler2DArrayShadow; diff --git a/libraries/shaders/headers/410/header.glsl b/libraries/shaders/headers/410/header.glsl new file mode 100644 index 0000000000..901ae6f9db --- /dev/null +++ b/libraries/shaders/headers/410/header.glsl @@ -0,0 +1,15 @@ +#version 410 core +#define GPU_GL410 +#define BITFIELD int +#if defined(VULKAN) + #extension GL_ARB_shading_language_420pack : require + #define LAYOUT(X) layout(X) + #define LAYOUT_STD140(X) layout(std140, X) +#else + #define LAYOUT(X) + #define LAYOUT_STD140(X) layout(std140) +#endif +#ifdef VULKAN +#define gl_InstanceID gl_InstanceIndex +#define gl_VertexID gl_VertexIndex +#endif diff --git a/libraries/shaders/headers/450/header.glsl b/libraries/shaders/headers/450/header.glsl new file mode 100644 index 0000000000..6ce61b4378 --- /dev/null +++ b/libraries/shaders/headers/450/header.glsl @@ -0,0 +1,10 @@ +#version 450 core +#define GPU_GL450 +#define GPU_SSBO_TRANSFORM_OBJECT +#define BITFIELD int +#define LAYOUT(X) layout(X) +#define LAYOUT_STD140(X) layout(std140, X) +#ifdef VULKAN +#define gl_InstanceID gl_InstanceIndex +#define gl_VertexID gl_VertexIndex +#endif diff --git a/libraries/shaders/headers/mono.glsl b/libraries/shaders/headers/mono.glsl new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libraries/shaders/headers/stereo.glsl b/libraries/shaders/headers/stereo.glsl new file mode 100644 index 0000000000..090035b4a4 --- /dev/null +++ b/libraries/shaders/headers/stereo.glsl @@ -0,0 +1,4 @@ +#define GPU_TRANSFORM_IS_STEREO +#define GPU_TRANSFORM_STEREO_CAMERA +#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED +#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN diff --git a/libraries/shaders/src/shaders/Shaders.cpp b/libraries/shaders/src/shaders/Shaders.cpp index ac4810a896..c385fadb37 100644 --- a/libraries/shaders/src/shaders/Shaders.cpp +++ b/libraries/shaders/src/shaders/Shaders.cpp @@ -8,101 +8,357 @@ #include "Shaders.h" -#include #include #include #include #include -#include -#include -#include -#include +#include +#include +#include + +#include #include -static bool cleanShaders() { -#if defined(Q_OS_MAC) - static const bool CLEAN_SHADERS = true; -#else - static const bool CLEAN_SHADERS = ::gl::disableGl45(); - -#endif - return CLEAN_SHADERS; -} - // Can't use the Q_INIT_RESOURCE macro inside a namespace on Mac, // so this is done out of line -void initShaders() { - static std::once_flag once; - std::call_once(once, [] { - Q_INIT_RESOURCE(shaders); - }); -} -static std::vector splitStringIntoLines(const std::string& s) { - std::stringstream ss(s); - std::vector result; - - std::string line; - while (std::getline(ss, line, '\n')) { - result.push_back(line); - } - return result; -} - -static std::string loadResource(const std::string& path) { - return FileUtils::readFile(path.c_str()).toStdString(); +static void initShadersResources() { + Q_INIT_RESOURCE(shaders); } namespace shader { -void cleanShaderSource(std::string& shaderSource) { - static const std::regex LAYOUT_REGEX{ R"REGEX(^layout\((\s*std140\s*,\s*)?(?:binding|location)\s*=\s*(?:\b\w+\b)\)\s*(?!(?:flat\s+)?(?:out|in|buffer))\b(.*)$)REGEX" }; - static const int GROUP_STD140 = 1; - static const int THE_REST_OF_THE_OWL = 2; - std::vector lines = splitStringIntoLines(shaderSource); - std::vector outLines; - std::unordered_map locationDefines; - for (const auto& line : lines) { - std::cmatch m; - if (std::regex_match(line.c_str(), m, LAYOUT_REGEX)) { - std::string outLine; - if (m[GROUP_STD140].matched) { - outLine = "layout(std140) "; - } - outLine += m[THE_REST_OF_THE_OWL].str(); - outLines.push_back(outLine); - continue; - // On mac we have to strip out all the explicit binding location layouts, - // because GL 4.1 doesn't support them - } - outLines.push_back(line); - } - std::ostringstream joined; - std::copy(outLines.begin(), outLines.end(), std::ostream_iterator(joined, "\n")); - shaderSource = joined.str(); +#if defined(USE_GLES) + +static const Dialect DEFAULT_DIALECT = Dialect::glsl310es; + +const std::vector& allDialects() { + static const std::vector ALL_DIALECTS{ { Dialect::glsl310es } }; + return ALL_DIALECTS; } -std::string loadShaderSource(uint32_t shaderId) { - initShaders(); - auto shaderStr = loadResource(std::string(":/shaders/") + std::to_string(shaderId)); - if (cleanShaders()) { - // OSX only supports OpenGL 4.1 without ARB_shading_language_420pack or - // ARB_explicit_uniform_location or basically anything useful that's - // been released in the last 8 fucking years, so in that case we need to - // strip out all explicit locations and do a bunch of background magic to - // make the system seem like it is using the explicit locations - cleanShaderSource(shaderStr); - } - return shaderStr; -} - -std::string loadShaderReflection(uint32_t shaderId) { - initShaders(); - auto path = std::string(":/shaders/") + std::to_string(shaderId) + std::string("_reflection"); - auto json = loadResource(path); - return json; +#elif defined(Q_OS_MAC) + +static const Dialect DEFAULT_DIALECT = Dialect::glsl410; + +const std::vector& allDialects() { + static const std::vector ALL_DIALECTS{ Dialect::glsl410 }; + return ALL_DIALECTS; } +#else + +static const Dialect DEFAULT_DIALECT = Dialect::glsl450; + +const std::vector & allDialects() { + static const std::vector ALL_DIALECTS{ { Dialect::glsl450, Dialect::glsl410 } }; + return ALL_DIALECTS; } +#endif + +const std::vector& allVariants() { + static const std::vector ALL_VARIANTS{ { Variant::Mono, Variant::Stereo } }; + return ALL_VARIANTS; +} + +const std::string& dialectPath(Dialect dialect) { + static const std::string e310esPath { "/310es/" }; + static const std::string e410Path { "/410/" }; + static const std::string e450Path { "/450/" }; + switch (dialect) { +#if defined(USE_GLES) + case Dialect::glsl310es: return e310esPath; +#else +#if !defined(Q_OS_MAC) + case Dialect::glsl450: return e450Path; +#endif + case Dialect::glsl410: return e410Path; +#endif + default: break; + } + throw std::runtime_error("Invalid dialect"); +} + +static std::string loadResource(const std::string& path) { + if (!QFileInfo(path.c_str()).exists()) { + return {}; + } + return FileUtils::readFile(path.c_str()).toStdString(); +} + +static Binary loadSpirvResource(const std::string& path) { + Binary result; + { + QFile file(path.c_str()); + + if (file.open(QFile::ReadOnly)) { + QByteArray bytes = file.readAll(); + result.resize(bytes.size()); + memcpy(bytes.data(), result.data(), bytes.size()); + } + } + return result; +} + +DialectVariantSource loadDialectVariantSource(const std::string& basePath) { + DialectVariantSource result; + result.scribe = loadResource(basePath + "scribe"); + result.spirv = loadSpirvResource(basePath + "spirv"); + result.glsl = loadResource(basePath + "glsl"); + String reflectionJson = loadResource(basePath + "json"); + result.reflection.parse(reflectionJson); + return result; +} + +DialectSource loadDialectSource(Dialect dialect, uint32_t shaderId) { + std::string basePath = std::string(":/shaders/") + std::to_string(shaderId) + dialectPath(dialect); + DialectSource result; + result.variantSources[Variant::Mono] = loadDialectVariantSource(basePath); + auto stereo = loadDialectVariantSource(basePath + "stereo/"); + if (stereo.valid()) { + result.variantSources[Variant::Stereo] = stereo; + } + return result; +} + +Source::Pointer Source::loadSource(uint32_t shaderId) { + auto result = std::make_shared(); + result->id = shaderId; + const auto& dialects = allDialects(); + result->name = loadResource(std::string(":/shaders/") + std::to_string(shaderId) + std::string("/name")); + for (const auto& dialect : dialects) { + result->dialectSources[dialect] = loadDialectSource(dialect, shaderId); + } + result->reflection = result->dialectSources[DEFAULT_DIALECT].variantSources[Variant::Mono].reflection; + return result; +} + +Source& Source::operator=(const Source& other) { + // DO NOT COPY the shader ID + name = other.name; + dialectSources = other.dialectSources; + replacements = other.replacements; + reflection = other.reflection; + return *this; +} + +const Source& Source::get(uint32_t shaderId) { + static std::once_flag once; + static const std::unordered_map shadersById; + std::call_once(once, [] { + initShadersResources(); + auto& map = const_cast&>(shadersById); + for (const auto& shaderId : allShaders()) { + map[shaderId] = loadSource(shaderId); + } + }); + const auto itr = shadersById.find(shaderId); + static const Source EMPTY_SHADER; + if (itr == shadersById.end()) { + return EMPTY_SHADER; + } + return *(itr->second); +} + +bool Source::doReplacement(String& source) const { + bool replaced = false; + for (const auto& entry : replacements) { + const auto& key = entry.first; + // First try search for a block to replace + // Blocks are required because oftentimes we need a stub function + // in the original source code to allow it to compile. As such we + // need to replace the stub with our own code rather than just inject + // some code. + const auto beginMarker = key + "_BEGIN"; + auto beginIndex = source.find(beginMarker); + if (beginIndex != std::string::npos) { + const auto endMarker = key + "_END"; + auto endIndex = source.find(endMarker, beginIndex); + if (endIndex != std::string::npos) { + auto size = endIndex - beginIndex; + source.replace(beginIndex, size, entry.second); + replaced = true; + continue; + } + } + + // If no block is found, try for a simple line replacement + beginIndex = source.find(key); + if (beginIndex != std::string::npos) { + source.replace(beginIndex, key.size(), entry.second); + replaced = true; + continue; + } + } + + return replaced; +} + +const DialectVariantSource& Source::getDialectVariantSource(Dialect dialect, Variant variant) const { + auto dialectEntry = dialectSources.find(dialect); + if (dialectEntry == dialectSources.end()) { + throw std::runtime_error("Dialect source not found"); + } + + const auto& dialectSource = dialectEntry->second; + auto variantEntry = dialectSource.variantSources.find(variant); + // FIXME revert to mono if stereo source is requested but not present + // (for when mono and stereo sources are the same) + if (variantEntry == dialectSource.variantSources.end()) { + throw std::runtime_error("Variant source not found"); + } + + return variantEntry->second; +} + + +String Source::getSource(Dialect dialect, Variant variant) const { + String result; + const auto& variantSource = getDialectVariantSource(dialect, variant); + if (!replacements.empty()) { + std::string result = variantSource.scribe; + if (doReplacement(result)) { + return result; + } + } + + if (variantSource.glsl.empty()) { + return variantSource.scribe; + } + + return variantSource.glsl; +} + +const Reflection& Source::getReflection(Dialect dialect, Variant variant) const { + const auto& variantSource = getDialectVariantSource(dialect, variant); + return variantSource.reflection; +} + +static const std::string NAME_KEY{ "name" }; +static const std::string INPUTS_KEY{ "inputs" }; +static const std::string OUTPUTS_KEY{ "outputs" }; +static const std::string UBOS_KEY{ "ubos" }; +static const std::string SSBOS_KEY{ "ssbos" }; + +static const std::string TEXTURES_KEY{ "textures" }; +static const std::string LOCATION_KEY{ "location" }; +static const std::string BINDING_KEY{ "binding" }; +static const std::string TYPE_KEY{ "type" }; + +std::unordered_set populateBufferTextureSet(const nlohmann::json& input) { + std::unordered_set result; + static const std::string SAMPLER_BUFFER{ "samplerBuffer" }; + auto arraySize = input.size(); + for (size_t i = 0; i < arraySize; ++i) { + auto entry = input[i]; + std::string name = entry[NAME_KEY]; + std::string type = entry[TYPE_KEY]; + if (type == SAMPLER_BUFFER) { + result.insert(name); + } + } + return result; +} + +Reflection::LocationMap populateLocationMap(const nlohmann::json& input, const std::string& locationKey) { + Reflection::LocationMap result; + auto arraySize = input.size(); + for (size_t i = 0; i < arraySize; ++i) { + auto entry = input[i]; + std::string name = entry[NAME_KEY]; + // Location or binding, depending on the locationKey parameter + int32_t location = entry[locationKey]; + result[name] = location; + } + return result; +} + +void Reflection::parse(const std::string& jsonString) { + if (jsonString.empty()) { + return; + } + using json = nlohmann::json; + auto root = json::parse(jsonString); + + if (root.count(INPUTS_KEY)) { + inputs = populateLocationMap(root[INPUTS_KEY], LOCATION_KEY); + } + if (root.count(OUTPUTS_KEY)) { + outputs = populateLocationMap(root[OUTPUTS_KEY], LOCATION_KEY); + } + if (root.count(SSBOS_KEY)) { + resourceBuffers = populateLocationMap(root[SSBOS_KEY], BINDING_KEY); + } + if (root.count(UBOS_KEY)) { + uniformBuffers = populateLocationMap(root[UBOS_KEY], BINDING_KEY); + } + if (root.count(TEXTURES_KEY)) { + textures = populateLocationMap(root[TEXTURES_KEY], BINDING_KEY); + auto bufferTextures = populateBufferTextureSet(root[TEXTURES_KEY]); + if (!bufferTextures.empty()) { + if (!resourceBuffers.empty()) { + throw std::runtime_error("Input shader has both SSBOs and texture buffers defined"); + } + for (const auto& bufferTexture : bufferTextures){ + resourceBuffers[bufferTexture] = textures[bufferTexture]; + textures.erase(bufferTexture); + } + } + } + updateValid(); + +} + + +static void mergeMap(Reflection::LocationMap& output, const Reflection::LocationMap& input) { + for (const auto& entry : input) { + if (0 != output.count(entry.first)) { + if (output[entry.first] != entry.second) { + throw std::runtime_error("Invalid reflection for merging"); + } + } else { + output[entry.first] = entry.second; + } + } +} + +static void updateValidSet(Reflection::ValidSet& output, const Reflection::LocationMap& input) { + output.clear(); + output.reserve(input.size()); + for (const auto& entry : input) { + output.insert(entry.second); + } +} + +void Reflection::merge(const Reflection& reflection) { + mergeMap(textures, reflection.textures); + mergeMap(uniforms, reflection.uniforms); + mergeMap(uniformBuffers, reflection.uniformBuffers); + mergeMap(resourceBuffers, reflection.resourceBuffers); + updateValid(); +} + +void Reflection::updateValid() { + updateValidSet(validInputs, inputs); + updateValidSet(validOutputs, outputs); + updateValidSet(validTextures, textures); + updateValidSet(validUniformBuffers, uniformBuffers); + updateValidSet(validResourceBuffers, resourceBuffers); + updateValidSet(validUniforms, uniforms); +} + + +std::vector Reflection::getNames(const LocationMap& locations) { + std::vector result; + result.reserve(locations.size()); + for (const auto& entry : locations) { + result.push_back(entry.first); + } + return result; +} + + +} // namespace shader + diff --git a/libraries/shaders/src/shaders/Shaders.h b/libraries/shaders/src/shaders/Shaders.h index 1335c1b49b..025abf7b0b 100644 --- a/libraries/shaders/src/shaders/Shaders.h +++ b/libraries/shaders/src/shaders/Shaders.h @@ -8,27 +8,169 @@ #pragma once #include +#include #include +#include #include -#include +#include + +#include + +#include namespace shader { static const uint32_t INVALID_SHADER = (uint32_t)-1; static const uint32_t INVALID_PROGRAM = (uint32_t)-1; -extern uint32_t all_programs[]; +const std::vector& allPrograms(); +const std::vector& allShaders(); -std::string loadShaderSource(uint32_t shaderId); -std::string loadShaderReflection(uint32_t shaderId); +enum class Dialect +{ +#if defined(USE_GLES) + // GLES only support 3.1 es + glsl310es, +#elif defined(Q_OS_MAC) + // Mac only supports 4.1 + glsl410, +#else + // Everything else supports 4.1 and 4.5 + glsl450, + glsl410, +#endif +}; + +const std::vector& allDialects(); +const std::string& dialectPath(Dialect dialect); + +enum class Variant { + Mono, + Stereo, +}; + +const std::vector& allVariants(); + +static const uint32_t NUM_VARIANTS = 2; + +using Binary = std::vector; +using String = std::string; + +struct EnumClassHash +{ + template + std::size_t operator()(T t) const + { + return static_cast(t); + } +}; + +struct Reflection { + using LocationMap = std::unordered_map; + using ValidSet = std::unordered_set; + + void parse(const std::string& json); + void merge(const Reflection& reflection); + + bool validInput(int32_t location) const { return validLocation(validInputs, location); } + bool validOutput(int32_t location) const { return validLocation(validOutputs, location); } + bool validTexture(int32_t location) const { return validLocation(validTextures, location); } + bool validUniform(int32_t location) const { return validLocation(validUniforms, location); } + bool validUniformBuffer(int32_t location) const { return validLocation(validUniformBuffers, location); } + bool validResourceBuffer(int32_t location) const { return validLocation(validResourceBuffers, location); } + + + LocationMap inputs; + + LocationMap outputs; + + LocationMap textures; + + LocationMap uniformBuffers; + + // Either SSBOs or Textures with the type samplerBuffer, depending on dialect + LocationMap resourceBuffers; + + // Needed for procedural code, will map to push constants for Vulkan + LocationMap uniforms; + + static std::vector getNames(const LocationMap& locations); + +private: + + bool validLocation(const ValidSet& locations, int32_t location) const { + return locations.count(location) != 0; + } + + void updateValid(); + + ValidSet validInputs; + ValidSet validOutputs; + ValidSet validTextures; + ValidSet validUniformBuffers; + ValidSet validResourceBuffers; + ValidSet validUniforms; +}; + +struct DialectVariantSource { + // The output of the scribe application with platforms specific headers + String scribe; + // Optimized SPIRV version of the shader + Binary spirv; + // Regenerated GLSL from the optimized SPIRV + String glsl; + // Shader reflection from the optimized SPIRV + Reflection reflection; + + bool valid() const { return !scribe.empty(); } +}; + +struct DialectSource { + std::unordered_map variantSources; +}; + +struct Source { + using Pointer = std::shared_ptr; + Source() = default; + Source& operator=(const Source& other); + + uint32_t id{ INVALID_SHADER }; + + // The name of the shader file, with extension, i.e. DrawColor.frag + std::string name; + + // Generic reflection, copied from the 450 dialect / mono variant + Reflection reflection; + + // Map of platforms to their specific shaders + std::unordered_map dialectSources; + + // Support for swapping out code blocks for procedural and debugging shaders + std::unordered_map replacements; + + String getSource(Dialect dialect, Variant variant) const; + const Reflection& getReflection(Dialect dialect, Variant variant) const; + bool valid() const { return !dialectSources.empty(); } + static Source generate(const std::string& glsl) { throw std::runtime_error("Implement me"); } + static const Source& get(uint32_t shaderId); + +private: + // Disallow copy construction and assignment + Source(const Source& other) = default; + + static Source::Pointer loadSource(uint32_t shaderId) ; + + bool doReplacement(String& source) const; + const DialectVariantSource& getDialectVariantSource(Dialect dialect, Variant variant) const; + +}; inline uint32_t getVertexId(uint32_t programId) { return (programId >> 16) & UINT16_MAX; } - + inline uint32_t getFragmentId(uint32_t programId) { return programId & UINT16_MAX; } -} - +} // namespace shader diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index 893b7f48b1..664f9fe906 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -19,7 +19,7 @@ if (WIN32 AND (NOT USE_GLES)) set(TARGET_NAME oculus) setup_hifi_plugin(Multimedia) link_hifi_libraries( - shared task gl gpu ${PLATFORM_GL_BACKEND} controllers ui qml + shared task gl shaders gpu ${PLATFORM_GL_BACKEND} controllers ui qml plugins ui-plugins display-plugins input-plugins audio-client networking render-utils ${PLATFORM_GL_BACKEND} diff --git a/plugins/oculusLegacy/CMakeLists.txt b/plugins/oculusLegacy/CMakeLists.txt index 00e90fb6d7..33d27c4e9d 100644 --- a/plugins/oculusLegacy/CMakeLists.txt +++ b/plugins/oculusLegacy/CMakeLists.txt @@ -13,7 +13,7 @@ if (APPLE) set(TARGET_NAME oculusLegacy) setup_hifi_plugin() - link_hifi_libraries(shared gl gpu plugins ui ui-plugins display-plugins input-plugins midi ${PLATFORM_GL_BACKEND}) + link_hifi_libraries(shared shaders gl gpu plugins ui ui-plugins display-plugins input-plugins midi ${PLATFORM_GL_BACKEND}) include_hifi_library_headers(octree) diff --git a/plugins/openvr/CMakeLists.txt b/plugins/openvr/CMakeLists.txt index ff94152d57..eea08e66d5 100644 --- a/plugins/openvr/CMakeLists.txt +++ b/plugins/openvr/CMakeLists.txt @@ -13,7 +13,7 @@ if (WIN32 AND (NOT USE_GLES)) setup_hifi_plugin(Gui Qml Multimedia) link_hifi_libraries(shared task gl qml networking controllers ui plugins display-plugins ui-plugins input-plugins script-engine - audio-client render-utils graphics gpu render model-networking fbx ktx image procedural ${PLATFORM_GL_BACKEND}) + audio-client render-utils graphics shaders gpu render model-networking fbx ktx image procedural ${PLATFORM_GL_BACKEND}) include_hifi_library_headers(octree) diff --git a/tests-manual/gpu-textures/CMakeLists.txt b/tests-manual/gpu-textures/CMakeLists.txt index 84f5027411..907690748a 100644 --- a/tests-manual/gpu-textures/CMakeLists.txt +++ b/tests-manual/gpu-textures/CMakeLists.txt @@ -4,7 +4,7 @@ setup_hifi_project(Quick Gui Script) setup_memory_debugger() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries( - shared task networking gl + shared shaders task networking gl ktx gpu octree ${PLATFORM_GL_BACKEND} ) diff --git a/tests-manual/gpu-textures/src/TestTextures.cpp b/tests-manual/gpu-textures/src/TestTextures.cpp index 701e60fab8..5d5ddce6fa 100644 --- a/tests-manual/gpu-textures/src/TestTextures.cpp +++ b/tests-manual/gpu-textures/src/TestTextures.cpp @@ -81,8 +81,10 @@ TexturesTest::TexturesTest() { connect(&stats, &TextureTestStats::prevTexture, this, &TexturesTest::onPrevTexture); connect(&stats, &TextureTestStats::maxTextureMemory, this, &TexturesTest::onMaxTextureMemory); { - auto VS = gpu::Shader::createVertex({ vertexShaderSource, {} }); - auto PS = gpu::Shader::createPixel({ fragmentShaderSource, {} }); + shader::Source vertexSource; + + auto VS = gpu::Shader::createVertex(shader::Source::generate(vertexShaderSource)); + auto PS = gpu::Shader::createPixel(shader::Source::generate(fragmentShaderSource)); auto program = gpu::Shader::createProgram(VS, PS); // If the pipeline did not exist, make it auto state = std::make_shared(); diff --git a/tests-manual/gpu/CMakeLists.txt b/tests-manual/gpu/CMakeLists.txt index 30218f3f97..8fd0316c05 100644 --- a/tests-manual/gpu/CMakeLists.txt +++ b/tests-manual/gpu/CMakeLists.txt @@ -5,7 +5,7 @@ setup_memory_debugger() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") link_hifi_libraries( shared task networking gl - ktx gpu procedural octree image + ktx shaders gpu procedural octree image graphics model-networking fbx animation script-engine render render-utils ${PLATFORM_GL_BACKEND} diff --git a/tests-manual/render-utils/CMakeLists.txt b/tests-manual/render-utils/CMakeLists.txt index be75c53f2e..9f575ee8ca 100644 --- a/tests-manual/render-utils/CMakeLists.txt +++ b/tests-manual/render-utils/CMakeLists.txt @@ -8,7 +8,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") setup_memory_debugger() # link in the shared libraries -link_hifi_libraries(render-utils gl gpu shared ${PLATFORM_GL_BACKEND}) +link_hifi_libraries(render-utils shaders gl gpu shared ${PLATFORM_GL_BACKEND}) target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT}) if (WIN32) diff --git a/tests/shaders/CMakeLists.txt b/tests/shaders/CMakeLists.txt index 08678c1c26..e238405d62 100644 --- a/tests/shaders/CMakeLists.txt +++ b/tests/shaders/CMakeLists.txt @@ -3,7 +3,9 @@ macro (setup_testcase_dependencies) # link in the shared libraries link_hifi_libraries(shared test-utils gpu shaders gl ${PLATFORM_GL_BACKEND}) + #target_spirv() package_libraries_for_deployment() endmacro () setup_hifi_testcase(Gui) + diff --git a/tests/shaders/src/ShaderTests.cpp b/tests/shaders/src/ShaderTests.cpp index 03dc034cd0..692771f0fc 100644 --- a/tests/shaders/src/ShaderTests.cpp +++ b/tests/shaders/src/ShaderTests.cpp @@ -31,22 +31,24 @@ #include #include +#define RUNTIME_SHADER_COMPILE_TEST 0 + +#if RUNTIME_SHADER_COMPILE_TEST +#include +#include +#include +#include +#endif + QTEST_MAIN(ShaderTests) -#pragma optimize("", off) void ShaderTests::initTestCase() { - _window = new QWindow(); - _window->setSurfaceType(QSurface::SurfaceType::OpenGLSurface); - _context = new ::gl::Context(_window); + _context = new ::gl::OffscreenContext(); getDefaultOpenGLSurfaceFormat(); _context->create(); if (!_context->makeCurrent()) { qFatal("Unable to make test GL context current"); } - QOpenGLContextWrapper(_context->qglContext()).makeCurrent(_window); - if (!_context->makeCurrent()) { - qFatal("Unable to make test GL context current"); - } gl::initModuleGl(); if (!_context->makeCurrent()) { qFatal("Unable to make test GL context current"); @@ -62,6 +64,8 @@ void ShaderTests::cleanupTestCase() { qDebug() << "Done"; } +#if RUNTIME_SHADER_COMPILE_TEST + template QStringList toQStringList(const C& c) { QStringList result; @@ -80,7 +84,7 @@ std::unordered_set toStringSet(const C& c, F f) { return result; } -template +template bool isSubset(const C& parent, const C& child) { for (const auto& v : child) { if (0 == parent.count(v)) { @@ -120,6 +124,7 @@ gpu::Shader::ReflectionMap mergeReflection(const std::initializer_list std::unordered_map invertMap(const std::unordered_map& map) { @@ -127,58 +132,52 @@ std::unordered_map invertMap(const std::unordered_map& map) { for (const auto& entry : map) { result[entry.second] = entry.first; } + if (result.size() != map.size()) { + throw std::runtime_error("Map inversion failure, result size does not match input size"); + } return result; } -static void verifyBindings(const gpu::Shader::Source& source) { - const auto reflection = source.getReflection(); - for (const auto& entry : reflection) { - const auto& map = entry.second; - const auto reverseMap = invertMap(map); - if (map.size() != reverseMap.size()) { - QFAIL("Bindings are not unique"); - } - } - -} - - -static void verifyInterface(const gpu::Shader::Source& vertexSource, const gpu::Shader::Source& fragmentSource) { - if (0 == fragmentSource.getReflection().count(gpu::Shader::BindingType::INPUT)) { +static void verifyInterface(const gpu::Shader::Source& vertexSource, + const gpu::Shader::Source& fragmentSource, + shader::Dialect dialect, + shader::Variant variant) { + const auto& fragmentReflection = fragmentSource.getReflection(dialect, variant); + if (fragmentReflection.inputs.empty()) { return; } - auto fragIn = fragmentSource.getReflection().at(gpu::Shader::BindingType::INPUT); - if (0 == vertexSource.getReflection().count(gpu::Shader::BindingType::OUTPUT)) { - qDebug() << "No vertex output for fragment input"; - //QFAIL("No vertex output for fragment input"); - return; + + const auto& vertexReflection = vertexSource.getReflection(dialect, variant); + const auto& fragIn = fragmentReflection.inputs; + if (vertexReflection.outputs.empty()) { + throw std::runtime_error("No vertex outputs for fragment inputs"); } - auto vout = vertexSource.getReflection().at(gpu::Shader::BindingType::OUTPUT); + + const auto& vout = vertexReflection.outputs; auto vrev = invertMap(vout); - static const std::string IN_STEREO_SIDE_STRING = "_inStereoSide"; for (const auto entry : fragIn) { const auto& name = entry.first; - // The presence of "_inStereoSide" in fragment shaders is a bug due to the way we do reflection - // and use preprocessor macros in the shaders - if (name == IN_STEREO_SIDE_STRING) { - continue; - } if (0 == vout.count(name)) { - qDebug() << "Vertex output missing"; - //QFAIL("Vertex output missing"); - continue; + throw std::runtime_error("Vertex outputs missing"); } const auto& inLocation = entry.second; const auto& outLocation = vout.at(name); if (inLocation != outLocation) { - qDebug() << "Mismatch in vertex / fragment interface"; - //QFAIL("Mismatch in vertex / fragment interface"); - continue; + throw std::runtime_error("Mismatch in vertex / fragment interface"); } } } -template +static void verifyInterface(const gpu::Shader::Source& vertexSource, const gpu::Shader::Source& fragmentSource) { + for (const auto& dialect : shader::allDialects()) { + for (const auto& variant : shader::allVariants()) { + verifyInterface(vertexSource, fragmentSource, dialect, variant); + } + } +} + +#if RUNTIME_SHADER_COMPILE_TEST +template bool compareBindings(const C& actual, const gpu::Shader::LocationMap& expected) { if (actual.size() != expected.size()) { auto actualNames = toStringSet(actual, [](const auto& v) { return v.name; }); @@ -192,48 +191,341 @@ bool compareBindings(const C& actual, const gpu::Shader::LocationMap& expected) return true; } -void ShaderTests::testShaderLoad() { - std::set usedShaders; - uint32_t maxShader = 0; - try { +void configureGLSLCompilerResources(TBuiltInResource* glslCompilerResources) { + glslCompilerResources->maxLights = 32; + glslCompilerResources->maxClipPlanes = 6; + glslCompilerResources->maxTextureUnits = 32; + glslCompilerResources->maxTextureCoords = 32; + glslCompilerResources->maxVertexAttribs = 64; + glslCompilerResources->maxVertexUniformComponents = 4096; + glslCompilerResources->maxVaryingFloats = 64; + glslCompilerResources->maxVertexTextureImageUnits = 32; + glslCompilerResources->maxCombinedTextureImageUnits = 80; + glslCompilerResources->maxTextureImageUnits = 32; + glslCompilerResources->maxFragmentUniformComponents = 4096; + glslCompilerResources->maxDrawBuffers = 32; + glslCompilerResources->maxVertexUniformVectors = 128; + glslCompilerResources->maxVaryingVectors = 8; + glslCompilerResources->maxFragmentUniformVectors = 16; + glslCompilerResources->maxVertexOutputVectors = 16; + glslCompilerResources->maxFragmentInputVectors = 15; + glslCompilerResources->minProgramTexelOffset = -8; + glslCompilerResources->maxProgramTexelOffset = 7; + glslCompilerResources->maxClipDistances = 8; + glslCompilerResources->maxComputeWorkGroupCountX = 65535; + glslCompilerResources->maxComputeWorkGroupCountY = 65535; + glslCompilerResources->maxComputeWorkGroupCountZ = 65535; + glslCompilerResources->maxComputeWorkGroupSizeX = 1024; + glslCompilerResources->maxComputeWorkGroupSizeY = 1024; + glslCompilerResources->maxComputeWorkGroupSizeZ = 64; + glslCompilerResources->maxComputeUniformComponents = 1024; + glslCompilerResources->maxComputeTextureImageUnits = 16; + glslCompilerResources->maxComputeImageUniforms = 8; + glslCompilerResources->maxComputeAtomicCounters = 8; + glslCompilerResources->maxComputeAtomicCounterBuffers = 1; + glslCompilerResources->maxVaryingComponents = 60; + glslCompilerResources->maxVertexOutputComponents = 64; + glslCompilerResources->maxGeometryInputComponents = 64; + glslCompilerResources->maxGeometryOutputComponents = 128; + glslCompilerResources->maxFragmentInputComponents = 128; + glslCompilerResources->maxImageUnits = 8; + glslCompilerResources->maxCombinedImageUnitsAndFragmentOutputs = 8; + glslCompilerResources->maxCombinedShaderOutputResources = 8; + glslCompilerResources->maxImageSamples = 0; + glslCompilerResources->maxVertexImageUniforms = 0; + glslCompilerResources->maxTessControlImageUniforms = 0; + glslCompilerResources->maxTessEvaluationImageUniforms = 0; + glslCompilerResources->maxGeometryImageUniforms = 0; + glslCompilerResources->maxFragmentImageUniforms = 8; + glslCompilerResources->maxCombinedImageUniforms = 8; + glslCompilerResources->maxGeometryTextureImageUnits = 16; + glslCompilerResources->maxGeometryOutputVertices = 256; + glslCompilerResources->maxGeometryTotalOutputComponents = 1024; + glslCompilerResources->maxGeometryUniformComponents = 1024; + glslCompilerResources->maxGeometryVaryingComponents = 64; + glslCompilerResources->maxTessControlInputComponents = 128; + glslCompilerResources->maxTessControlOutputComponents = 128; + glslCompilerResources->maxTessControlTextureImageUnits = 16; + glslCompilerResources->maxTessControlUniformComponents = 1024; + glslCompilerResources->maxTessControlTotalOutputComponents = 4096; + glslCompilerResources->maxTessEvaluationInputComponents = 128; + glslCompilerResources->maxTessEvaluationOutputComponents = 128; + glslCompilerResources->maxTessEvaluationTextureImageUnits = 16; + glslCompilerResources->maxTessEvaluationUniformComponents = 1024; + glslCompilerResources->maxTessPatchComponents = 120; + glslCompilerResources->maxPatchVertices = 32; + glslCompilerResources->maxTessGenLevel = 64; + glslCompilerResources->maxViewports = 16; + glslCompilerResources->maxVertexAtomicCounters = 0; + glslCompilerResources->maxTessControlAtomicCounters = 0; + glslCompilerResources->maxTessEvaluationAtomicCounters = 0; + glslCompilerResources->maxGeometryAtomicCounters = 0; + glslCompilerResources->maxFragmentAtomicCounters = 8; + glslCompilerResources->maxCombinedAtomicCounters = 8; + glslCompilerResources->maxAtomicCounterBindings = 1; + glslCompilerResources->maxVertexAtomicCounterBuffers = 0; + glslCompilerResources->maxTessControlAtomicCounterBuffers = 0; + glslCompilerResources->maxTessEvaluationAtomicCounterBuffers = 0; + glslCompilerResources->maxGeometryAtomicCounterBuffers = 0; + glslCompilerResources->maxFragmentAtomicCounterBuffers = 1; + glslCompilerResources->maxCombinedAtomicCounterBuffers = 1; + glslCompilerResources->maxAtomicCounterBufferSize = 16384; + glslCompilerResources->maxTransformFeedbackBuffers = 4; + glslCompilerResources->maxTransformFeedbackInterleavedComponents = 64; + glslCompilerResources->maxCullDistances = 8; + glslCompilerResources->maxCombinedClipAndCullDistances = 8; + glslCompilerResources->maxSamples = 4; + glslCompilerResources->limits.nonInductiveForLoops = 1; + glslCompilerResources->limits.whileLoops = 1; + glslCompilerResources->limits.doWhileLoops = 1; + glslCompilerResources->limits.generalUniformIndexing = 1; + glslCompilerResources->limits.generalAttributeMatrixVectorIndexing = 1; + glslCompilerResources->limits.generalVaryingIndexing = 1; + glslCompilerResources->limits.generalSamplerIndexing = 1; + glslCompilerResources->limits.generalVariableIndexing = 1; + glslCompilerResources->limits.generalConstantMatrixVectorIndexing = 1; +} -#if 0 - uint32_t testPrograms[] = { - shader::render_utils::program::parabola, - shader::INVALID_PROGRAM, - }; -#else - const auto& testPrograms = shader::all_programs; +void writeSpirv(const std::string& filename, const std::vector& spirv) { + std::ofstream o(filename, std::ios::trunc | std::ios::binary); + for (const auto& word : spirv) { + o.write((const char*)&word, sizeof(word)); + } + o.close(); +} + +void write(const std::string& filename, const std::string& text) { + std::ofstream o(filename, std::ios::trunc); + o.write(text.c_str(), text.size()); + o.close(); +} + +bool endsWith(const std::string& s, const std::string& f) { + auto end = s.substr(s.size() - f.size()); + return (end == f); +} + +EShLanguage getShaderStage(const std::string& shaderName) { + static const std::string VERT_EXT{ ".vert" }; + static const std::string FRAG_EXT{ ".frag" }; + static const std::string GEOM_EXT{ ".geom" }; + static const size_t EXT_SIZE = VERT_EXT.size(); + if (shaderName.size() < EXT_SIZE) { + throw std::runtime_error("Invalid shader name"); + } + std::string ext = shaderName.substr(shaderName.size() - EXT_SIZE); + if (ext == VERT_EXT) { + return EShLangVertex; + } else if (ext == FRAG_EXT) { + return EShLangFragment; + } else if (ext == GEOM_EXT) { + return EShLangGeometry; + } + throw std::runtime_error("Invalid shader name"); +} + +const gpu::Shader::Source& loadShader(uint32_t shaderId); + +bool compileSpirv(uint32_t shaderId, bool stereo) { + const gpu::Shader::Source& source = loadShader(shaderId); + using namespace glslang; + + static const std::string CORE_HEADER( + R"SHADER(#version 450 core +#define GPU_GL450 +#define BITFIELD int +#define GPU_SSBO_TRANSFORM_OBJECT +#define gl_VertexID gl_VertexIndex +#define gl_InstanceID gl_InstanceIndex +)SHADER"); + + static const std::string DOMAIN_HEADER[] = { + "#define GPU_VERTEX_SHADER\r\n", + "#define GPU_PIXEL_SHADER\r\n", + "#define GPU_GEOMETRY_SHADER\r\n", + }; + + static const std::string STEREO_HEADER( + R"SHADER( +#define GPU_TRANSFORM_IS_STEREO +#define GPU_TRANSFORM_STEREO_CAMERA +#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED +#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN +)SHADER"); + + static std::once_flag once; + static TBuiltInResource glslCompilerResources; + std::call_once(once, [&] { configureGLSLCompilerResources(&glslCompilerResources); }); + + static const EShMessages messages = (EShMessages)(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); + auto shaderName = shader::loadShaderName(shaderId); + auto stage = getShaderStage(shaderName); + + TShader shader(stage); + std::vector strings; + strings.push_back(CORE_HEADER.c_str()); + strings.push_back(DOMAIN_HEADER[stage == EShLangVertex ? 0 : 1].c_str()); + if (stereo) { + strings.push_back(STEREO_HEADER.c_str()); + } + strings.push_back(source.getCode().c_str()); + shader.setStrings(strings.data(), (int)strings.size()); + shader.setEnvInput(EShSourceGlsl, stage, EShClientOpenGL, 450); + shader.setEnvClient(EShClientVulkan, EShTargetVulkan_1_1); + shader.setEnvTarget(EShTargetSpv, EShTargetSpv_1_3); + bool success = shader.parse(&glslCompilerResources, 450, false, messages); + if (!success) { + qWarning() << "Failed to parse shader " << shaderName.c_str(); + qWarning() << shader.getInfoLog(); + qWarning() << shader.getInfoDebugLog(); + return false; + } + + // Create and link a shader program containing the single shader + glslang::TProgram program; + program.addShader(&shader); + if (!program.link(messages)) { + qWarning() << "Failed to compile shader " << shaderName.c_str(); + qWarning() << program.getInfoLog(); + qWarning() << program.getInfoDebugLog(); + return false; + } + + std::string baseOutName = "d:/shaders/" + shaderName; + if (stereo) { + baseOutName += ".stereo"; + } + + // Output the SPIR-V code from the shader program + std::vector spirv; + glslang::GlslangToSpv(*program.getIntermediate(stage), spirv); + + spvtools::SpirvTools core(SPV_ENV_VULKAN_1_1); + spvtools::Optimizer opt(SPV_ENV_VULKAN_1_1); + + auto outputLambda = [](spv_message_level_t, const char*, const spv_position_t&, const char* m) { qWarning() << m; }; + core.SetMessageConsumer(outputLambda); + opt.SetMessageConsumer(outputLambda); + + if (!core.Validate(spirv)) { + throw std::runtime_error("invalid spirv"); + } + writeSpirv(baseOutName + ".spv", spirv); + + opt.RegisterPass(spvtools::CreateFreezeSpecConstantValuePass()) + .RegisterPass(spvtools::CreateStrengthReductionPass()) + .RegisterPass(spvtools::CreateEliminateDeadConstantPass()) + .RegisterPass(spvtools::CreateEliminateDeadFunctionsPass()) + .RegisterPass(spvtools::CreateUnifyConstantPass()); + + std::vector optspirv; + if (!opt.Run(spirv.data(), spirv.size(), &optspirv)) { + throw std::runtime_error("bad optimize run"); + } + writeSpirv(baseOutName + ".opt.spv", optspirv); + + std::string disassembly; + if (!core.Disassemble(optspirv, &disassembly)) { + throw std::runtime_error("bad disassembly"); + } + + write(baseOutName + ".spv.txt", disassembly); + + return true; +} #endif - size_t index = 0; - while (shader::INVALID_PROGRAM != testPrograms[index]) { - auto programId = testPrograms[index]; - ++index; +void validateDialectVariantSource(const shader::DialectVariantSource& source) { + if (source.scribe.empty()) { + throw std::runtime_error("Missing scribe source"); + } - uint32_t vertexId = shader::getVertexId(programId); - uint32_t fragmentId = shader::getFragmentId(programId); - usedShaders.insert(vertexId); - usedShaders.insert(fragmentId); - maxShader = std::max(maxShader, std::max(fragmentId, vertexId)); - auto vertexSource = gpu::Shader::getShaderSource(vertexId); - QVERIFY(!vertexSource.getCode().empty()); - verifyBindings(vertexSource); - auto fragmentSource = gpu::Shader::getShaderSource(fragmentId); - QVERIFY(!fragmentSource.getCode().empty()); - verifyBindings(fragmentSource); + if (source.spirv.empty()) { + throw std::runtime_error("Missing SPIRV"); + } + + if (source.glsl.empty()) { + throw std::runtime_error("Missing GLSL"); + } +} + +void validaDialectSource(const shader::DialectSource& dialectSource) { + for (const auto& variant : shader::allVariants()) { + validateDialectVariantSource(dialectSource.variantSources.find(variant)->second); + } +} + +void validateSource(const shader::Source& shader) { + if (shader.id == shader::INVALID_SHADER) { + throw std::runtime_error("Missing stored shader ID"); + } + + if (shader.name.empty()) { + throw std::runtime_error("Missing shader name"); + } + + static const auto& dialects = shader::allDialects(); + for (const auto dialect : dialects) { + if (!shader.dialectSources.count(dialect)) { + throw std::runtime_error("Missing platform shader"); + } + const auto& platformShader = shader.dialectSources.find(dialect)->second; + validaDialectSource(platformShader); + } +} + +void ShaderTests::testShaderLoad() { + try { + const auto& shaderIds = shader::allShaders(); + + // For debugging compile or link failures on individual programs, enable the following block and change the array values + // Be sure to end with the sentinal value of shader::INVALID_PROGRAM + const auto& programIds = shader::allPrograms(); + + for (auto shaderId : shaderIds) { + validateSource(shader::Source::get(shaderId)); + } + + { + std::unordered_set programUsedShaders; +#pragma omp parallel for + for (auto programId : programIds) { + auto vertexId = shader::getVertexId(programId); + shader::Source::get(vertexId); + programUsedShaders.insert(vertexId); + auto fragmentId = shader::getFragmentId(programId); + shader::Source::get(fragmentId); + programUsedShaders.insert(fragmentId); + } + + for (const auto& shaderId : shaderIds) { + if (programUsedShaders.count(shaderId)) { + continue; + } + const auto& shader = shader::Source::get(shaderId); + qDebug() << "Unused shader found" << shader.name.c_str(); + } + } + + // Traverse all programs again to do program level tests + for (const auto& programId : programIds) { + auto vertexId = shader::getVertexId(programId); + const auto& vertexSource = shader::Source::get(vertexId); + auto fragmentId = shader::getFragmentId(programId); + const auto& fragmentSource = shader::Source::get(fragmentId); verifyInterface(vertexSource, fragmentSource); - auto expectedBindings = mergeReflection({ vertexSource, fragmentSource }); - auto program = gpu::Shader::createProgram(programId); auto glBackend = std::static_pointer_cast(_gpuContext->getBackend()); auto glshader = gpu::gl::GLShader::sync(*glBackend, *program); + if (!glshader) { - qWarning() << "Failed to compile or link vertex " << vertexId << " fragment " << fragmentId; + qWarning() << "Failed to compile or link vertex " << vertexSource.name.c_str() << " fragment " + << fragmentSource.name.c_str(); QFAIL("Program link error"); } - +#if RUNTIME_SHADER_COMPILE_TEST + auto expectedBindings = mergeReflection({ vertexSource, fragmentSource }); QVERIFY(glshader != nullptr); for (const auto& shaderObject : glshader->_shaderObjects) { const auto& program = shaderObject.glprogram; @@ -260,9 +552,10 @@ void ShaderTests::testShaderLoad() { // Textures { auto textures = gl::Uniform::loadTextures(program); - auto expiredBegin = std::remove_if(textures.begin(), textures.end(), [&](const gl::Uniform& uniform) -> bool { - return uniform.name == "transformObjectBuffer"; - }); + auto expiredBegin = + std::remove_if(textures.begin(), textures.end(), [&](const gl::Uniform& uniform) -> bool { + return uniform.name == "transformObjectBuffer"; + }); textures.erase(expiredBegin, textures.end()); const auto expectedTextures = expectedBindings[gpu::Shader::BindingType::TEXTURE]; @@ -296,11 +589,13 @@ void ShaderTests::testShaderLoad() { // FIXME add storage buffer validation } +#endif } } catch (const std::runtime_error& error) { QFAIL(error.what()); } +#if RUNTIME_SHADER_COMPILE_TEST for (uint32_t i = 1; i <= maxShader; ++i) { auto used = usedShaders.count(i); if (0 != usedShaders.count(i)) { @@ -310,6 +605,7 @@ void ShaderTests::testShaderLoad() { auto name = QJsonDocument::fromJson(reflectionJson.c_str()).object()["name"].toString(); qDebug() << "Unused shader" << name; } +#endif qDebug() << "Completed all shaders"; } diff --git a/tests/shaders/src/ShaderTests.h b/tests/shaders/src/ShaderTests.h index d109341c1f..fd361d9e60 100644 --- a/tests/shaders/src/ShaderTests.h +++ b/tests/shaders/src/ShaderTests.h @@ -23,9 +23,8 @@ private slots: void testShaderLoad(); private: - QWindow* _window{ nullptr }; - gl::Context* _context{ nullptr }; + gl::OffscreenContext* _context{ nullptr }; gpu::ContextPointer _gpuContext; }; -#endif // hifi_ViewFruxtumTests_h +#endif // hifi_ViewFruxtumTests_h diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 9b36180bc2..1c36306410 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,9 +2,6 @@ add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") -add_subdirectory(shreflect) -set_target_properties(shreflect PROPERTIES FOLDER "Tools") - find_npm() if (NPM_EXECUTABLE) add_subdirectory(jsdoc) diff --git a/tools/ktx-tool/CMakeLists.txt b/tools/ktx-tool/CMakeLists.txt index 9daf8e0a1a..6bb09e0a9e 100644 --- a/tools/ktx-tool/CMakeLists.txt +++ b/tools/ktx-tool/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME ktx-tool) setup_hifi_project(Quick Gui Concurrent) -link_hifi_libraries(shared networking image gl gpu ktx) +link_hifi_libraries(shared networking image gl shaders gpu ktx) target_gli() diff --git a/tools/scribe/src/TextTemplate.cpp b/tools/scribe/src/TextTemplate.cpp index 89937c4da6..aad508487c 100755 --- a/tools/scribe/src/TextTemplate.cpp +++ b/tools/scribe/src/TextTemplate.cpp @@ -81,6 +81,7 @@ bool TextTemplate::loadFile(const ConfigPointer& config, const char* filename, S std::ifstream ifs; ifs.open(fullfilename.c_str()); if (ifs.is_open()) { + config->_includeFullPaths.insert(fullfilename); std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); source = str; ifs.close(); @@ -1003,3 +1004,9 @@ void TextTemplate::Config::displayTree(std::ostream& dst, int& level) const { } level--; } + +void TextTemplate::Config::displayMakefileDeps(std::ostream& dst) const { + for (const auto& include : _includeFullPaths) { + std::cout << include << std::endl; + } +} diff --git a/tools/scribe/src/TextTemplate.h b/tools/scribe/src/TextTemplate.h index 44edc23c12..b3b767176c 100755 --- a/tools/scribe/src/TextTemplate.h +++ b/tools/scribe/src/TextTemplate.h @@ -18,6 +18,7 @@ #include #include #include +#include class TextTemplate { public: @@ -26,6 +27,7 @@ public: typedef std::vector< String > StringVector; typedef std::map< String, String > Vars; typedef std::map< String, TextTemplate::Pointer > Includes; + using PathSet = std::unordered_set; class Tag { public: @@ -123,7 +125,7 @@ public: public: typedef std::shared_ptr< Config > Pointer; typedef bool (*IncluderCallback) (const Config::Pointer& config, const char* filename, String& source); - + PathSet _includeFullPaths; Includes _includes; Funcs _funcs; std::ostream* _logStream; @@ -139,6 +141,8 @@ public: void addIncludePath(const char* path); void displayTree(std::ostream& dst, int& level) const; + + void displayMakefileDeps(std::ostream& dst) const; }; static bool loadFile(const Config::Pointer& config, const char* filename, String& source); @@ -156,9 +160,12 @@ public: void displayTree(std::ostream& dst, int& level) const; + void displayMakefileDeps(std::ostream& dst) const { _config->displayMakefileDeps(dst); } + protected: Config::Pointer _config; Block::Pointer _root; + PathSet _includeFullPaths; int _numErrors; bool _steppingStarted; diff --git a/tools/scribe/src/main.cpp b/tools/scribe/src/main.cpp index c8c540c362..c1ade05bb1 100755 --- a/tools/scribe/src/main.cpp +++ b/tools/scribe/src/main.cpp @@ -25,10 +25,12 @@ int main (int argc, char** argv) { std::string srcFilename; std::string destFilename; std::string targetName; + std::list headerFiles; TextTemplate::Vars vars; std::string lastVarName; bool listVars = false; + bool makefileDeps = false; bool showParseTree = false; bool makeCPlusPlus = false; @@ -42,6 +44,7 @@ int main (int argc, char** argv) { GRAB_INCLUDE_PATH, GRAB_TARGET_NAME, GRAB_SHADER_TYPE, + GRAB_HEADER, EXIT, } mode = READY; @@ -65,6 +68,8 @@ int main (int argc, char** argv) { case READY: { if (inputs.back() == "-o") { mode = GRAB_OUTPUT; + } else if (inputs.back() == "-H") { + mode = GRAB_HEADER; } else if (inputs.back() == "-t") { mode = GRAB_TARGET_NAME; } else if (inputs.back() == "-D") { @@ -74,6 +79,9 @@ int main (int argc, char** argv) { } else if (inputs.back() == "-listVars") { listVars = true; mode = READY; + } else if (inputs.back() == "-M") { + makefileDeps = true; + mode = READY; } else if (inputs.back() == "-showParseTree") { showParseTree = true; mode = READY; @@ -85,7 +93,7 @@ int main (int argc, char** argv) { } else { // just grabbed the source filename, stop parameter parsing srcFilename = inputs.back(); - mode = EXIT; + mode = READY; } } break; @@ -102,6 +110,12 @@ int main (int argc, char** argv) { } break; + case GRAB_HEADER: { + headerFiles.push_back(inputs.back()); + mode = READY; + } + break; + case GRAB_VAR_NAME: { // grab first the name of the var lastVarName = inputs.back(); @@ -155,6 +169,9 @@ int main (int argc, char** argv) { cerr << " -I include_directory: Declare a directory to be added to the includes search pool." << endl; cerr << " -D varname varvalue: Declare a var used to generate the output file." << endl; cerr << " varname and varvalue must be made of alpha numerical characters with no spaces." << endl; + cerr << " -H : Prepend the contents of header file to the scribe output " << endl; + cerr << " This can be specified multiple times and the headers will be applied in the specified order" << endl; + cerr << " -M : Emit a list of files that the scribe output depends on, for make and similar build tools " << endl; cerr << " -listVars : Will list the vars name and value in the standard output." << endl; cerr << " -showParseTree : Draw the tree obtained while parsing the source" << endl; cerr << " -c++ : Generate a c++ source file containing the output file stream stored as a char[] variable" << endl; @@ -204,8 +221,37 @@ int main (int argc, char** argv) { auto scribe = std::make_shared(srcFilename, config); + std::string header; + if (!headerFiles.empty()) { + for (const auto& headerFile : headerFiles) { + std::fstream headerStream; + headerStream.open(headerFile, std::fstream::in); + if (!headerStream.is_open()) { + cerr << "Failed to open source file <" << headerFile << ">" << endl; + return 1; + } + header += std::string((std::istreambuf_iterator(headerStream)), std::istreambuf_iterator()); + } + } + + // Add the type define to the shader + switch (type) { + case VERTEX: + header += "#define GPU_VERTEX_SHADER\n"; + break; + + case FRAGMENT: + header += "#define GPU_PIXEL_SHADER\n"; + break; + + case GEOMETRY: + header += "#define GPU_GEOMETRY_SHADER\n"; + break; + } + // ready to parse and generate std::stringstream destStringStream; + destStringStream << header; int numErrors = scribe->scribe(destStringStream, srcStream, vars); if (numErrors) { cerr << "Scribe " << srcFilename << "> failed: " << numErrors << " errors." << endl; @@ -222,7 +268,9 @@ int main (int argc, char** argv) { scribe->displayTree(cerr, level); } - if (makeCPlusPlus) { + if (makefileDeps) { + scribe->displayMakefileDeps(cout); + } else if (makeCPlusPlus) { // Because there is a maximum size for literal strings declared in source we need to partition the // full source string stream into pages that seems to be around that value... const int MAX_STRING_LITERAL = 10000; diff --git a/tools/shadergen.py b/tools/shadergen.py new file mode 100644 index 0000000000..9f69e6d0bc --- /dev/null +++ b/tools/shadergen.py @@ -0,0 +1,254 @@ +import re +import subprocess +import sys +import time +import os +import json +import argparse +import concurrent +from os.path import expanduser +from concurrent.futures import ThreadPoolExecutor +from argparse import ArgumentParser +from pathlib import Path +from threading import Lock + + # # Target dependant Custom rule on the SHADER_FILE + # if (ANDROID) + # set(GLPROFILE LINUX_GL) + # else() + # if (APPLE) + # set(GLPROFILE MAC_GL) + # elseif(UNIX) + # set(GLPROFILE LINUX_GL) + # else() + # set(GLPROFILE PC_GL) + # endif() + # endif() + +def getTypeForScribeFile(scribefilename): + last = scribefilename.rfind('.') + extension = scribefilename[last:] + switcher = { + '.slv': 'vert', + '.slf': 'frag', + '.slg': 'geom', + '.slc': 'comp', + } + if not extension in switcher: + raise ValueError("Unknown scribe file type for " + scribefilename) + return switcher.get(extension) + +def getCommonScribeArgs(scribefile, includeLibs): + scribeArgs = [args.scribe] + # FIXME use the sys.platform to set the correct value + scribeArgs.extend(['-D', 'GLPROFILE', 'PC_GL']) + scribeArgs.extend(['-T', getTypeForScribeFile(scribefile)]) + for lib in includeLibs: + scribeArgs.extend(['-I', args.source_dir + '/libraries/' + lib + '/src/' + lib + '/']) + scribeArgs.extend(['-I', args.source_dir + '/libraries/' + lib + '/src/']) + scribeArgs.append(scribefile) + return scribeArgs + +def getDialectAndVariantHeaders(dialect, variant): + headerPath = args.source_dir + '/libraries/shaders/headers/' + variantHeader = headerPath + ('stereo.glsl' if (variant == 'stereo') else 'mono.glsl') + dialectHeader = headerPath + dialect + '/header.glsl' + return [dialectHeader, variantHeader] + +class ScribeDependenciesCache: + cache = {} + lock = Lock() + filename = '' + + def __init__(self, filename): + self.filename = filename + + def load(self): + jsonstr = '{}' + if (os.path.exists(self.filename)): + with open(self.filename) as f: + jsonstr = f.read() + self.cache = json.loads(jsonstr) + + def save(self): + with open(self.filename, "w") as f: + f.write(json.dumps(self.cache)) + + def get(self, scribefile, dialect, variant): + self.lock.acquire() + key = self.key(scribefile, dialect, variant) + try: + if key in self.cache: + return self.cache[key].copy() + finally: + self.lock.release() + return None + + def key(self, scribeFile, dialect, variant): + return ':'.join([scribeFile, dialect, variant]) + + def getOrGen(self, scribefile, includeLibs, dialect, variant): + result = self.get(scribefile, dialect, variant) + if (None == result): + result = self.gen(scribefile, includeLibs, dialect, variant) + return result + + def gen(self, scribefile, includeLibs, dialect, variant): + scribeArgs = getCommonScribeArgs(scribefile, includeLibs) + scribeArgs.extend(['-M']) + processResult = subprocess.run(scribeArgs, stdout=subprocess.PIPE) + if (0 != processResult.returncode): + raise RuntimeError("Unable to parse scribe dependencies") + result = processResult.stdout.decode("utf-8").splitlines(False) + result.append(scribefile) + result.extend(getDialectAndVariantHeaders(dialect, variant)) + key = self.key(scribefile, dialect, variant) + self.lock.acquire() + self.cache[key] = result.copy() + self.lock.release() + return result + +def getFileTimes(files): + if isinstance(files, str): + files = [files] + return list(map(lambda f: os.path.getmtime(f) if os.path.isfile(f) else -1, files)) + +def outOfDate(inputs, output): + oldestInput = max(getFileTimes(inputs)) + youngestOutput = min(getFileTimes(output)) + diff = youngestOutput - oldestInput + return oldestInput >= youngestOutput + +def executeSubprocess(processArgs): + processResult = subprocess.run(processArgs, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if (0 != processResult.returncode): + raise RuntimeError('Call to "{}" failed.\n\narguments:\n{}\n\nstdout:\n{}\n\nstderr:\n{}'.format( + processArgs[0], + ' '.join(processArgs[1:]), + processResult.stdout.decode('utf-8'), + processResult.stderr.decode('utf-8'))) + +folderMutex = Lock() + +def processCommand(line): + global args + global scribeDepCache + glslangExec = args.spirv_binaries + '/glslangValidator' + spirvCrossExec = args.spirv_binaries + '/spirv-cross' + spirvOptExec = args.spirv_binaries + '/spirv-opt' + params = line.split(';') + dialect = params.pop(0) + variant = params.pop(0) + scribeFile = args.source_dir + '/' + params.pop(0) + unoptGlslFile = args.source_dir + '/' + params.pop(0) + libs = params + + upoptSpirvFile = unoptGlslFile + '.spv' + spirvFile = unoptGlslFile + '.opt.spv' + reflectionFile = unoptGlslFile + '.json' + glslFile = unoptGlslFile + '.glsl' + outputFiles = [unoptGlslFile, spirvFile, reflectionFile, glslFile] + + scribeOutputDir = os.path.abspath(os.path.join(unoptGlslFile, os.pardir)) + + # Serialize checking and creation of the output directory to avoid occasional + # crashes + global folderMutex + folderMutex.acquire() + if not os.path.exists(scribeOutputDir): + os.makedirs(scribeOutputDir) + folderMutex.release() + + scribeDeps = scribeDepCache.getOrGen(scribeFile, libs, dialect, variant) + + # if the scribe sources (slv, slf, slh, etc), or the dialect/ variant headers are out of date + # regenerate the scribe GLSL output + if args.force or outOfDate(scribeDeps, outputFiles): + print('Processing file {} dialect {} variant {}'.format(scribeFile, dialect, variant)) + if args.dry_run: + return True + + scribeDepCache.gen(scribeFile, libs, dialect, variant) + scribeArgs = getCommonScribeArgs(scribeFile, libs) + headerFlag = '-H' + # using the old flag on Android builds for now + if (dialect == '310es'): headerFlag = '-h' + for header in getDialectAndVariantHeaders(dialect, variant): + scribeArgs.extend([headerFlag, header]) + scribeArgs.extend(['-o', unoptGlslFile]) + executeSubprocess(scribeArgs) + + # Generate the un-optimized output + executeSubprocess([glslangExec, '-V110', '-o', upoptSpirvFile, unoptGlslFile]) + + # Optimize the SPIRV + executeSubprocess([spirvOptExec, '-O', '-o', spirvFile, upoptSpirvFile]) + + # Generation JSON reflection + executeSubprocess([spirvCrossExec, '--reflect', 'json', '--output', reflectionFile, spirvFile]) + + # Generate the optimized GLSL output + spirvCrossArgs = [spirvCrossExec, '--output', glslFile, spirvFile, '--version', dialect] + if (dialect == '410'): spirvCrossArgs.append('--no-420pack-extension') + executeSubprocess(spirvCrossArgs) + else: + # This logic is necessary because cmake will agressively keep re-executing the shadergen + # code otherwise + Path(unoptGlslFile).touch() + Path(upoptSpirvFile).touch() + Path(spirvFile).touch() + Path(glslFile).touch() + Path(reflectionFile).touch() + return True + + + +def main(): + commands = args.commands.read().splitlines(False) + if args.debug or True: + for command in commands: + processCommand(command) + else: + workers = max(1, os.cpu_count() - 2) + with ThreadPoolExecutor(max_workers=workers) as executor: + for result in executor.map(processCommand, commands): + if not result: + raise RuntimeError("Failed to execute all subprocesses") + executor.shutdown() + + +parser = ArgumentParser(description='Generate shader artifacts.') +parser.add_argument('--commands', type=argparse.FileType('r'), help='list of commands to execute') +parser.add_argument('--spirv-binaries', type=str, help='location of the SPIRV binaries') +parser.add_argument('--build-dir', type=str, help='The build directory base path') +parser.add_argument('--source-dir', type=str, help='The root directory of the git repository') +parser.add_argument('--scribe', type=str, help='The scribe executable path') +parser.add_argument('--debug', action='store_true') +parser.add_argument('--force', action='store_true', help='Ignore timestamps and force regeneration of all files') +parser.add_argument('--dry-run', action='store_true', help='Report the files that would be process, but do not output') + +args = None +if len(sys.argv) == 1: + # for debugging + spirvPath = os.environ['VULKAN_SDK'] + '/bin' + #spirvPath = expanduser('~//VulkanSDK/1.1.82.1/x86_64/bin') + sourceDir = expanduser('~/git/hifi') + buildPath = sourceDir + '/build_noui' + scribePath = buildPath + '/tools/scribe/Release/scribe' + commandsPath = buildPath + '/libraries/shaders/shadergen.txt' + shaderDir = buildPath + '/libraries/shaders' + testArgs = '--commands {} --spirv-binaries {} --scribe {} --build-dir {} --source-dir {}'.format( + commandsPath, spirvPath, scribePath, shaderDir, sourceDir + ).split() + #testArgs.append('--debug') + #testArgs.append('--force') + #testArgs.append('--dry-run') + args = parser.parse_args(testArgs) +else: + args = parser.parse_args() + +scribeDepCache = ScribeDependenciesCache(args.build_dir + '/shaderDeps.json') +scribeDepCache.load() +main() +scribeDepCache.save() + diff --git a/tools/shreflect/CMakeLists.txt b/tools/shreflect/CMakeLists.txt deleted file mode 100644 index 0748f59d31..0000000000 --- a/tools/shreflect/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -set(TARGET_NAME shreflect) - -# don't use the setup_hifi_project macro as we don't want Qt or GLM dependencies -file(GLOB TARGET_SRCS src/*) -add_executable(${TARGET_NAME} ${TARGET_SRCS}) -target_json() - -if (WIN32) - set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG "/OPT:NOREF /OPT:NOICF") -endif() diff --git a/tools/shreflect/src/main.cpp b/tools/shreflect/src/main.cpp deleted file mode 100644 index e13f937102..0000000000 --- a/tools/shreflect/src/main.cpp +++ /dev/null @@ -1,204 +0,0 @@ -// -// Bradley Austin Davis on 2018/05/24 -// Copyright 2013 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using json = nlohmann::json; - -std::vector splitStringIntoLines(const std::string& s) { - std::stringstream ss(s); - std::vector result; - std::string line; - while (std::getline(ss, line, '\n')) { - result.push_back(line); - } - return result; -} - -std::string readFile(const std::string& file) { - std::ifstream t(file); - std::string str((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); - return str; -} - -void writeFile(const std::string& file, const std::string& out) { - std::ofstream t(file, std::ios::trunc); - t << out; - t.close(); -} - -// Convert a Perl style multi-line commented regex into a C++ style regex -// All whitespace will be removed and lines with '#' comments will have the comments removed -std::string getUnformattedRegex(const std::string& formatted) { - static const std::regex WHITESPACE = std::regex("\\s+"); - static const std::string EMPTY; - std::string result; - result.reserve(formatted.size()); - auto lines = splitStringIntoLines(formatted); - for (auto line : lines) { - auto commentStart = line.find('#'); - if (std::string::npos != commentStart) { - line = line.substr(0, commentStart); - } - line = std::regex_replace(line, WHITESPACE, EMPTY); - result += line; - } - - return result; -} - -static std::string LAYOUT_REGEX_STRING{ R"REGEX( -^layout\( # BEGIN layout declaration block - (\s*std140\s*,\s*)? # Optional std140 marker - (binding|location) # binding / location - \s*=\s* - (?: - (\b\d+\b) # literal numeric binding like binding=1 - | - (\b[A-Z_0-9]+\b) # Preprocessor macro binding like binding=GPU_TEXTURE_FOO - ) -\)\s* # END layout declaration block -(?: - ( # Texture or simple uniform like `layout(binding=0) uniform sampler2D originalTexture;` - uniform\s+ - (\b\w+\b)\s+ - (\b\w+\b)\s* - (?:\[\d*\])? - (?:\s*=.*)? - \s*;.*$ - ) - | - ( # UBO or SSBO like `layout(std140, binding=GPU_STORAGE_TRANSFORM_OBJECT) buffer transformObjectBuffer {` - \b(uniform|buffer)\b\s+ - \b(\w+\b) - \s*\{.*$ - ) - | - ( # Input or output attribute like `layout(location=GPU_ATTR_POSITION) in vec4 inPosition;` - \b(in|out)\b\s+ - \b(\w+)\b\s+ - \b(\w+)\b\s*;\s*$ - ) -) -)REGEX" }; - -enum Groups { - STD140 = 1, - LOCATION_TYPE = 2, - LOCATION_LITERAL = 3, - LOCATION_DEFINE = 4, - DECL_SIMPLE = 5, - SIMPLE_TYPE = 6, - SIMPLE_NAME = 7, - DECL_STRUCT = 8, - STRUCT_TYPE = 9, - STRUCT_NAME = 10, - DECL_INOUT = 11, - INOUT_DIRECTION = 12, - INOUT_TYPE = 13, - INOUT_NAME = 14, -}; - -json reflectShader(const std::string& shaderPath) { - static const std::regex DEFINE("^#define\\s+([_A-Z0-9]+)\\s+(\\d+)\\s*$"); - static const std::regex LAYOUT_QUALIFIER{ getUnformattedRegex(LAYOUT_REGEX_STRING) }; - - - auto shaderSource = readFile(shaderPath); - std::vector lines = splitStringIntoLines(shaderSource); - using Map = std::unordered_map; - - json inputs; - json outputs; - json textures; - json textureTypes; - json uniforms; - json storageBuffers; - json uniformBuffers; - Map locationDefines; - for (const auto& line : lines) { - std::cmatch m; - if (std::regex_match(line.c_str(), m, DEFINE)) { - locationDefines[m[1].str()] = std::stoi(m[2].first); - } else if (std::regex_match(line.c_str(), m, LAYOUT_QUALIFIER)) { - int binding = -1; - if (m[LOCATION_LITERAL].matched) { - binding = std::stoi(m[LOCATION_LITERAL].str()); - } else { - binding = locationDefines[m[LOCATION_DEFINE].str()]; - } - if (m[DECL_SIMPLE].matched) { - auto name = m[SIMPLE_NAME].str(); - auto type = m[SIMPLE_TYPE].str(); - bool isTexture = 0 == type.find("sampler"); - auto& map = isTexture ? textures : uniforms; - map[name] = binding; - if (isTexture) { - textureTypes[name] = type; - } - } else if (m[DECL_STRUCT].matched) { - auto name = m[STRUCT_NAME].str(); - auto type = m[STRUCT_TYPE].str(); - auto& map = (type == "buffer") ? storageBuffers : uniformBuffers; - map[name] = binding; - } else if (m[DECL_INOUT].matched) { - auto name = m[INOUT_NAME].str(); - auto& map = (m[INOUT_DIRECTION].str() == "in") ? inputs : outputs; - map[name] = binding; - } - } - } - - json result; - if (!inputs.empty()) { - result["inputs"] = inputs; - } - if (!outputs.empty()) { - result["outputs"] = outputs; - } - if (!textures.empty()) { - result["textures"] = textures; - } - if (!textureTypes.empty()) { - result["texturesTypes"] = textureTypes; - } - if (!uniforms.empty()) { - result["uniforms"] = uniforms; - } - if (!storageBuffers.empty()) { - result["storageBuffers"] = storageBuffers; - } - if (!uniformBuffers.empty()) { - result["uniformBuffers"] = uniformBuffers; - } - - result["name"] = shaderPath; - - return result; -} - -int main (int argc, char** argv) { - auto path = std::string(argv[1]); - auto shaderReflection = reflectShader(path); - writeFile(path + ".json", shaderReflection.dump(4)); - return 0; -} From 3f2f5b18ec71e5f3e5f8588e8e8a739a769bb480 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 19 Oct 2018 14:39:32 -0700 Subject: [PATCH 201/276] Reimplement procedural uniforms without extension requirements --- .../procedural/src/procedural/Procedural.cpp | 89 +++++++++---------- .../procedural/src/procedural/Procedural.h | 21 ++++- .../src/procedural/ProceduralCommon.slh | 69 +++++++------- 3 files changed, 97 insertions(+), 82 deletions(-) diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 426c9ff893..1c082ed250 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -40,7 +40,7 @@ static const std::string PROCEDURAL_VERSION = "//PROCEDURAL_VERSION"; bool operator==(const ProceduralData& a, const ProceduralData& b) { return ((a.version == b.version) && (a.shaderUrl == b.shaderUrl) && (a.uniforms == b.uniforms) && - (a.channels == b.channels)); + (a.channels == b.channels)); } QJsonValue ProceduralData::getProceduralData(const QString& proceduralJson) { @@ -105,7 +105,7 @@ Procedural::Procedural() { _transparentState->setCullMode(gpu::State::CULL_NONE); _transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); _transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); _standardInputsBuffer = std::make_shared(sizeof(StandardInputs), nullptr); } @@ -211,10 +211,10 @@ bool Procedural::isReady() const { } void Procedural::prepare(gpu::Batch& batch, - const glm::vec3& position, - const glm::vec3& size, - const glm::quat& orientation, - const glm::vec4& color) { + const glm::vec3& position, + const glm::vec3& size, + const glm::quat& orientation, + const glm::vec4& color) { _entityDimensions = size; _entityPosition = position; _entityOrientation = glm::mat3_cast(orientation); @@ -308,6 +308,7 @@ void Procedural::prepare(gpu::Batch& batch, } } + void Procedural::setupUniforms(bool transparent) { _uniforms.clear(); auto customUniformCount = _data.uniforms.keys().size(); @@ -323,53 +324,50 @@ void Procedural::setupUniforms(bool transparent) { } else if (value.isArray()) { auto valueArray = value.toArray(); switch (valueArray.size()) { - case 0: - break; + case 0: + break; - case 1: { - float v = valueArray[0].toDouble(); - _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform1f(slot, v); }); - break; - } + case 1: { + float v = valueArray[0].toDouble(); + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform1f(slot, v); }); + break; + } - case 2: { - glm::vec2 v{ valueArray[0].toDouble(), valueArray[1].toDouble() }; - _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform2f(slot, v.x, v.y); }); - break; - } + case 2: { + glm::vec2 v{ valueArray[0].toDouble(), valueArray[1].toDouble() }; + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform2f(slot, v.x, v.y); }); + break; + } - case 3: { - glm::vec3 v{ - valueArray[0].toDouble(), - valueArray[1].toDouble(), - valueArray[2].toDouble(), - }; - _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform3f(slot, v.x, v.y, v.z); }); - break; - } + case 3: { + glm::vec3 v{ + valueArray[0].toDouble(), + valueArray[1].toDouble(), + valueArray[2].toDouble(), + }; + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform3f(slot, v.x, v.y, v.z); }); + break; + } - default: - case 4: { - glm::vec4 v{ - valueArray[0].toDouble(), - valueArray[1].toDouble(), - valueArray[2].toDouble(), - valueArray[3].toDouble(), - }; - _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform4f(slot, v.x, v.y, v.z, v.w); }); - break; - } + default: + case 4: { + glm::vec4 v{ + valueArray[0].toDouble(), + valueArray[1].toDouble(), + valueArray[2].toDouble(), + valueArray[3].toDouble(), + }; + _uniforms.push_back([=](gpu::Batch& batch) { batch._glUniform4f(slot, v.x, v.y, v.z, v.w); }); + break; + } } } } _uniforms.push_back([=](gpu::Batch& batch) { - // Time and position - { - // Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds - float time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND; - _standardInputs.posAndTime = vec4(_entityPosition, time); - } + _standardInputs.position = vec4(_entityPosition, 1.0f); + // Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds + _standardInputs.time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND; // Date { @@ -385,7 +383,8 @@ void Procedural::setupUniforms(bool transparent) { _standardInputs.date.w = (time.hour() * 3600) + (time.minute() * 60) + time.second() + fractSeconds; } - _standardInputs.scaleAndCount = vec4(_entityDimensions, ++_frameCount); + _standardInputs.scale = vec4(_entityDimensions, 1.0f); + _standardInputs.frameCount = ++_frameCount; _standardInputs.orientation = mat4(_entityOrientation); for (size_t i = 0; i < MAX_PROCEDURAL_TEXTURE_CHANNELS; ++i) { diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index c92725b61b..781ac25249 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -73,14 +73,29 @@ public: protected: + // DO NOT TOUCH + // We have to pack these in a particular way to match the ProceduralCommon.slh + // layout. struct StandardInputs { vec4 date; - vec4 posAndTime; - vec4 scaleAndCount; - mat4 orientation; + vec4 position; + vec4 scale; + float time; + int frameCount; + vec2 _spare1; vec4 resolution[4]; + mat4 orientation; }; + static_assert(0 == offsetof(StandardInputs, date), "ProceduralOffsets"); + static_assert(16 == offsetof(StandardInputs, position), "ProceduralOffsets"); + static_assert(32 == offsetof(StandardInputs, scale), "ProceduralOffsets"); + static_assert(48 == offsetof(StandardInputs, time), "ProceduralOffsets"); + static_assert(52 == offsetof(StandardInputs, frameCount), "ProceduralOffsets"); + static_assert(56 == offsetof(StandardInputs, _spare1), "ProceduralOffsets"); + static_assert(64 == offsetof(StandardInputs, resolution), "ProceduralOffsets"); + static_assert(128 == offsetof(StandardInputs, orientation), "ProceduralOffsets"); + // Procedural metadata ProceduralData _data; diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh index fbfba81329..ee5a5de688 100644 --- a/libraries/procedural/src/procedural/ProceduralCommon.slh +++ b/libraries/procedural/src/procedural/ProceduralCommon.slh @@ -23,42 +23,43 @@ LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL1) uniform sampler2D iChannel1; LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL2) uniform sampler2D iChannel2; LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL3) uniform sampler2D iChannel3; -struct StandardInputs { +// DO NOT TOUCH +// This block does not follow our normal rules of always using a struct and +// always using 16 byte aligned types like vec4 and mat4 +// +// This is because this block must be EXACTLY how it is in order to maintain +// comptability with existing procedural shaders that previously relied on these +// inputs as uniforms, not members of a UBO + +LAYOUT_STD140(binding=0) uniform standardInputsBuffer { + // Offset 0 vec4 date; - vec4 posAndTime; - vec4 scaleAndCount; - mat4 orientation; - vec4 resolution[4]; -}; - - -LAYOUT(binding=0) uniform standardInputsBuffer { - StandardInputs params; -}; - -// shader playback time (in seconds) -float iGlobalTime = params.posAndTime.w; - -vec4 iDate = params.date; - -int iFrameCount = int(params.scaleAndCount.w); - -// the position of the object being rendered -vec3 iWorldPosition = params.posAndTime.xyz; - -// the dimensions of the object being rendered -vec3 iWorldScale = params.scaleAndCount.xyz; - -// the orientation of the object being rendered -mat3 iWorldOrientation = mat3(params.orientation); - -vec3 iChannelResolution[4] = vec3[4]( - params.resolution[0].xyz, - params.resolution[1].xyz, - params.resolution[2].xyz, - params.resolution[3].xyz -); + // Offset 16, acts as vec4 for alignment purposes + vec3 worldPosition; + // Offset 32, acts as vec4 for alignment purposes + vec3 worldScale; + // Offset 48 + float globalTime; + // Offset 52 + int frameCount; + // Offset 56 + vec2 _spare1; + // Offset 64, acts as vec4[4] for alignment purposes + vec3 channelResolution[4]; + // Offset 128, acts as vec4[3] for alignment purposes + // Also, each individual component is aligned as a vec4 + mat3 worldOrientation; + // Offset 176 + vec4 _spare2; +} standardInputs; +#define iDate standardInputs.date +#define iWorldPosition standardInputs.worldPosition +#define iWorldScale standardInputs.worldScale +#define iGlobalTime standardInputs.globalTime +#define iFrameCount standardInputs.frameCount +#define iChannelResolution standardInputs.channelResolution +#define iWorldOrientation standardInputs.worldOrientation // Unimplemented uniforms // Resolution doesn't make sense in the VR context From 4cf4a5582d4ce30a43e89af41ce11e37118de7a7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 22 Oct 2018 11:22:30 -0700 Subject: [PATCH 202/276] Fix Android build on Windows hosts --- libraries/ui/src/InteractiveWindow.cpp | 4 ++-- libraries/ui/src/InteractiveWindow.h | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/ui/src/InteractiveWindow.cpp b/libraries/ui/src/InteractiveWindow.cpp index 6c7f2d503f..5f7999f826 100644 --- a/libraries/ui/src/InteractiveWindow.cpp +++ b/libraries/ui/src/InteractiveWindow.cpp @@ -292,8 +292,8 @@ int InteractiveWindow::getPresentationMode() const { return _qmlWindow->property(PRESENTATION_MODE_PROPERTY).toInt(); } -#ifdef Q_OS_WIN void InteractiveWindow::parentNativeWindowToMainWindow() { +#ifdef Q_OS_WIN if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "parentNativeWindowToMainWindow"); return; @@ -307,8 +307,8 @@ void InteractiveWindow::parentNativeWindowToMainWindow() { } 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()) { diff --git a/libraries/ui/src/InteractiveWindow.h b/libraries/ui/src/InteractiveWindow.h index f456b32e8d..a25d559557 100644 --- a/libraries/ui/src/InteractiveWindow.h +++ b/libraries/ui/src/InteractiveWindow.h @@ -84,9 +84,7 @@ private: Q_INVOKABLE void setPresentationMode(int presentationMode); Q_INVOKABLE int getPresentationMode() const; -#ifdef Q_OS_WIN Q_INVOKABLE void parentNativeWindowToMainWindow(); -#endif public slots: From 85b92a372df5fdf6f3a4b02edcad4f0eba60ae72 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 22 Oct 2018 11:22:59 -0700 Subject: [PATCH 203/276] Don't use optimized GLSL on android for now --- libraries/shaders/src/shaders/Shaders.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/shaders/src/shaders/Shaders.cpp b/libraries/shaders/src/shaders/Shaders.cpp index c385fadb37..549bf65f80 100644 --- a/libraries/shaders/src/shaders/Shaders.cpp +++ b/libraries/shaders/src/shaders/Shaders.cpp @@ -224,11 +224,16 @@ String Source::getSource(Dialect dialect, Variant variant) const { } } +#ifdef Q_OS_ANDROID + // SPIRV cross injects "#extension GL_OES_texture_buffer : require" into the GLSL shaders, + // which breaks android rendering + return variantSource.scribe; +#else if (variantSource.glsl.empty()) { return variantSource.scribe; } - return variantSource.glsl; +#endif } const Reflection& Source::getReflection(Dialect dialect, Variant variant) const { From dd97c4fcd2580e3236f99b49836ff4b82d51ebd9 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 22 Oct 2018 11:37:08 -0700 Subject: [PATCH 204/276] Fix shadergen parallelism --- tools/shadergen.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/shadergen.py b/tools/shadergen.py index 9f69e6d0bc..7450aebcb3 100644 --- a/tools/shadergen.py +++ b/tools/shadergen.py @@ -188,7 +188,10 @@ def processCommand(line): executeSubprocess([spirvCrossExec, '--reflect', 'json', '--output', reflectionFile, spirvFile]) # Generate the optimized GLSL output - spirvCrossArgs = [spirvCrossExec, '--output', glslFile, spirvFile, '--version', dialect] + spirvCrossDialect = dialect + # 310es causes spirv-cross to inject "#extension GL_OES_texture_buffer : require" into the output + if (dialect == '310es'): spirvCrossDialect = '320es' + spirvCrossArgs = [spirvCrossExec, '--output', glslFile, spirvFile, '--version', spirvCrossDialect] if (dialect == '410'): spirvCrossArgs.append('--no-420pack-extension') executeSubprocess(spirvCrossArgs) else: @@ -205,7 +208,7 @@ def processCommand(line): def main(): commands = args.commands.read().splitlines(False) - if args.debug or True: + if args.debug: for command in commands: processCommand(command) else: From 5a4346221823a536f74172a174fb7140c9df7793 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 23 Oct 2018 09:28:30 -0700 Subject: [PATCH 205/276] Remove unneeded shader directive --- libraries/procedural/src/procedural/ProceduralCommon.slh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh index ee5a5de688..64a4fdd1f9 100644 --- a/libraries/procedural/src/procedural/ProceduralCommon.slh +++ b/libraries/procedural/src/procedural/ProceduralCommon.slh @@ -14,10 +14,6 @@ <$declareStandardCameraTransform()$> -#ifdef GL_EXT_shader_non_constant_global_initializers -#extension GL_EXT_shader_non_constant_global_initializers : enable -#endif - LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL0) uniform sampler2D iChannel0; LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL1) uniform sampler2D iChannel1; LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL2) uniform sampler2D iChannel2; From 39a16703e4c6efafd1e8f2beb4ec0b88964fdbf9 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 23 Oct 2018 10:52:14 -0700 Subject: [PATCH 206/276] updated wording for the radio buttons for avatar leaning model --- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/ui/PreferencesDialog.cpp | 45 +++++++++++++------------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c55a257d6a..dd40a748af 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -533,7 +533,7 @@ void MyAvatar::update(float deltaTime) { float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); - qCDebug(interfaceapp) << "lock " << _lockSitStandState.get() << " sit " << _isInSittingState.get() << " hmd lean "<< _hmdLeanRecenterEnabled; + // put the average hand azimuth into sensor space. // then mix it with head facing direction to determine rotation recenter if (getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() && getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid()) { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index eeca96222b..92628c23b7 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -243,29 +243,6 @@ void setupPreferences() { preference->setIndented(true); preferences->addPreference(preference); } - { - auto getter = [myAvatar]()->int { if (myAvatar->getUserRecenterModel() == MyAvatar::SitStandModelType::Auto) { - return 0; - } else if (myAvatar->getUserRecenterModel() == MyAvatar::SitStandModelType::ForceSit) { - return 1; - } else { - return 2; - }}; - auto setter = [myAvatar](int value) { if (value == 0) { - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); - } else if (value == 1) { - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); - } else { - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); - }}; - auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Disable Recenter", getter, setter); - QStringList items; - items << "Auto" << "Force Sitting" << "Disable Recenter"; - preference->setHeading("User Activity mode"); - preference->setItems(items); - preferences->addPreference(preference); - } - { auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); }; @@ -282,6 +259,28 @@ void setupPreferences() { auto preference = new CheckPreference(VR_MOVEMENT, "Show room boundaries while teleporting", getter, setter); preferences->addPreference(preference); } + { + auto getter = [myAvatar]()->int { if (myAvatar->getUserRecenterModel() == MyAvatar::SitStandModelType::Auto) { + return 0; + } else if (myAvatar->getUserRecenterModel() == MyAvatar::SitStandModelType::ForceSit) { + return 1; + } else { + return 2; + }}; + auto setter = [myAvatar](int value) { if (value == 0) { + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); + } else if (value == 1) { + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); + } else { + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); + }}; + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Disable Recenter", getter, setter); + QStringList items; + items << "Auto - turns on avatar leaning when standing in real world" << "Seated - disables all avatar leaning while sitting in real world" << "Disabled - allows avatar sitting on the floor [Experimental]"; + preference->setHeading("Avatar leaning behavior"); + preference->setItems(items); + preferences->addPreference(preference); + } { auto getter = [=]()->float { return myAvatar->getUserHeight(); }; auto setter = [=](float value) { myAvatar->setUserHeight(value); }; From 405b1d5725fda3c3455401f60b775d511d449607 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 23 Oct 2018 10:58:28 -0700 Subject: [PATCH 207/276] Revert "Better imports for controls and styles" --- .../QtWebEngine/UIDelegates/Menu.qml | 4 +- .../QtWebEngine/UIDelegates/MenuItem.qml | 4 +- .../QtWebEngine/UIDelegates/PromptDialog.qml | 4 +- interface/resources/qml/AudioScopeUI.qml | 4 +- interface/resources/qml/Browser.qml | 4 +- interface/resources/qml/CurrentAPI.qml | 4 +- interface/resources/qml/InfoView.qml | 2 +- interface/resources/qml/InteractiveWindow.qml | 4 +- interface/resources/qml/LoginDialog.qml | 4 +- .../LoginDialog/+android/LinkAccountBody.qml | 4 +- .../qml/LoginDialog/+android/SignUpBody.qml | 4 +- .../qml/LoginDialog/CompleteProfileBody.qml | 4 +- .../qml/LoginDialog/LinkAccountBody.qml | 5 +- .../resources/qml/LoginDialog/SignInBody.qml | 4 +- .../resources/qml/LoginDialog/SignUpBody.qml | 4 +- .../qml/LoginDialog/UsernameCollisionBody.qml | 4 +- .../resources/qml/LoginDialog/WelcomeBody.qml | 4 +- interface/resources/qml/QmlWebWindow.qml | 4 +- interface/resources/qml/QmlWindow.qml | 4 +- interface/resources/qml/TabletBrowser.qml | 4 +- interface/resources/qml/UpdateDialog.qml | 4 +- .../+android/ImageButton.qml | 6 +- .../AttachmentsTable.qml | 4 +- .../BaseWebView.qml | 0 .../{controlsUit => controls-uit}/Button.qml | 2 +- .../CheckBox.qml | 2 +- .../CheckBoxQQC2.qml | 4 +- .../ComboBox.qml | 4 +- .../ContentSection.qml | 2 +- .../FilterBar.qml | 4 +- .../GlyphButton.qml | 2 +- .../HorizontalRule.qml | 0 .../HorizontalSpacer.qml | 2 +- .../ImageMessageBox.qml | 2 +- .../qml/{controlsUit => controls-uit}/Key.qml | 0 .../Keyboard.qml | 0 .../{controlsUit => controls-uit}/Label.qml | 2 +- .../QueuedButton.qml | 2 +- .../RadioButton.qml | 4 +- .../ScrollBar.qml | 2 +- .../Separator.qml | 2 +- .../{controlsUit => controls-uit}/Slider.qml | 4 +- .../{controlsUit => controls-uit}/SpinBox.qml | 4 +- .../{controlsUit => controls-uit}/Switch.qml | 2 +- .../{controlsUit => controls-uit}/Table.qml | 2 +- .../TabletContentSection.qml | 2 +- .../TabletHeader.qml | 2 +- .../TextAction.qml | 4 +- .../TextEdit.qml | 2 +- .../TextField.qml | 4 +- .../{controlsUit => controls-uit}/ToolTip.qml | 0 .../{controlsUit => controls-uit}/Tree.qml | 2 +- .../VerticalSpacer.qml | 2 +- .../WebGlyphButton.qml | 2 +- .../WebSpinner.qml | 0 .../{controlsUit => controls-uit}/WebView.qml | 0 .../qml/{controlsUit => controls-uit}/qmldir | 0 interface/resources/qml/controls/Button.qml | 1 + .../qml/controls/FlickableWebViewCore.qml | 2 +- .../qml/controls/TabletWebButton.qml | 2 +- .../qml/controls/TabletWebScreen.qml | 2 +- .../resources/qml/controls/TabletWebView.qml | 4 +- interface/resources/qml/controls/WebView.qml | 2 +- .../resources/qml/dialogs/AssetDialog.qml | 2 +- .../qml/dialogs/CustomQueryDialog.qml | 4 +- .../resources/qml/dialogs/FileDialog.qml | 4 +- .../resources/qml/dialogs/MessageDialog.qml | 4 +- .../qml/dialogs/PreferencesDialog.qml | 4 +- .../resources/qml/dialogs/QueryDialog.qml | 4 +- .../qml/dialogs/TabletAssetDialog.qml | 2 +- .../qml/dialogs/TabletCustomQueryDialog.qml | 4 +- .../qml/dialogs/TabletFileDialog.qml | 4 +- .../qml/dialogs/TabletLoginDialog.qml | 4 +- .../qml/dialogs/TabletMessageBox.qml | 4 +- .../qml/dialogs/TabletQueryDialog.qml | 4 +- .../assetDialog/AssetDialogContent.qml | 4 +- .../dialogs/fileDialog/FileTypeSelection.qml | 2 +- .../messageDialog/MessageDialogButton.qml | 2 +- .../dialogs/preferences/AvatarPreference.qml | 2 +- .../preferences/BrowsablePreference.qml | 2 +- .../dialogs/preferences/ButtonPreference.qml | 2 +- .../preferences/CheckBoxPreference.qml | 2 +- .../preferences/ComboBoxPreference.qml | 4 +- .../preferences/EditablePreference.qml | 2 +- .../preferences/PrimaryHandPreference.qml | 2 +- .../preferences/RadioButtonsPreference.qml | 4 +- .../qml/dialogs/preferences/Section.qml | 4 +- .../dialogs/preferences/SliderPreference.qml | 2 +- .../dialogs/preferences/SpinBoxPreference.qml | 2 +- .../preferences/SpinnerSliderPreference.qml | 2 +- .../resources/qml/hifi/+android/ActionBar.qml | 4 +- .../resources/qml/hifi/+android/AudioBar.qml | 4 +- .../qml/hifi/+android/AvatarOption.qml | 2 +- .../resources/qml/hifi/+android/StatsBar.qml | 4 +- .../qml/hifi/+android/WindowHeader.qml | 4 +- .../qml/hifi/+android/bottomHudOptions.qml | 4 +- .../resources/qml/hifi/+android/modesbar.qml | 4 +- interface/resources/qml/hifi/AssetServer.qml | 4 +- interface/resources/qml/hifi/AvatarApp.qml | 4 +- interface/resources/qml/hifi/Card.qml | 2 +- interface/resources/qml/hifi/ComboDialog.qml | 4 +- interface/resources/qml/hifi/Desktop.qml | 2 +- .../qml/hifi/DesktopLetterboxMessage.qml | 2 +- interface/resources/qml/hifi/Feed.qml | 2 +- .../resources/qml/hifi/LetterboxMessage.qml | 2 +- interface/resources/qml/hifi/NameCard.qml | 4 +- interface/resources/qml/hifi/Pal.qml | 4 +- .../resources/qml/hifi/SkyboxChanger.qml | 4 +- .../resources/qml/hifi/SpectatorCamera.qml | 4 +- .../resources/qml/hifi/TabletTextButton.qml | 2 +- interface/resources/qml/hifi/TextButton.qml | 2 +- interface/resources/qml/hifi/WebBrowser.qml | 4 +- interface/resources/qml/hifi/audio/Audio.qml | 4 +- .../qml/hifi/audio/AudioTabButton.qml | 4 +- .../resources/qml/hifi/audio/CheckBox.qml | 2 +- .../qml/hifi/audio/PlaySampleSound.qml | 4 +- .../qml/hifi/avatarapp/AdjustWearables.qml | 5 +- .../qml/hifi/avatarapp/AvatarAppHeader.qml | 2 +- .../qml/hifi/avatarapp/AvatarAppStyle.qml | 2 +- .../avatarapp/AvatarWearablesIndicator.qml | 4 +- .../qml/hifi/avatarapp/BlueButton.qml | 4 +- .../hifi/avatarapp/CreateFavoriteDialog.qml | 4 +- .../qml/hifi/avatarapp/InputField.qml | 4 +- .../qml/hifi/avatarapp/InputTextStyle4.qml | 4 +- .../qml/hifi/avatarapp/MessageBox.qml | 4 +- .../resources/qml/hifi/avatarapp/Settings.qml | 4 +- .../qml/hifi/avatarapp/ShadowGlyph.qml | 2 +- .../qml/hifi/avatarapp/ShadowImage.qml | 2 +- .../qml/hifi/avatarapp/ShadowRectangle.qml | 2 +- .../qml/hifi/avatarapp/SquareLabel.qml | 4 +- .../resources/qml/hifi/avatarapp/Vector3.qml | 4 +- .../qml/hifi/avatarapp/WhiteButton.qml | 4 +- .../qml/hifi/commerce/checkout/Checkout.qml | 4 +- .../hifi/commerce/common/CommerceLightbox.qml | 4 +- .../common/EmulatedMarketplaceHeader.qml | 4 +- .../hifi/commerce/common/FirstUseTutorial.qml | 4 +- .../common/sendAsset/ConnectionItem.qml | 4 +- .../common/sendAsset/RecipientDisplay.qml | 4 +- .../commerce/common/sendAsset/SendAsset.qml | 4 +- .../InspectionCertificate.qml | 4 +- .../MarketplaceItemTester.qml | 4 +- .../hifi/commerce/purchases/PurchasedItem.qml | 4 +- .../qml/hifi/commerce/purchases/Purchases.qml | 4 +- .../qml/hifi/commerce/wallet/Help.qml | 4 +- .../qml/hifi/commerce/wallet/NeedsLogIn.qml | 4 +- .../hifi/commerce/wallet/PassphraseChange.qml | 4 +- .../hifi/commerce/wallet/PassphraseModal.qml | 4 +- .../commerce/wallet/PassphraseSelection.qml | 4 +- .../qml/hifi/commerce/wallet/Security.qml | 4 +- .../commerce/wallet/SecurityImageChange.qml | 4 +- .../wallet/SecurityImageSelection.qml | 4 +- .../qml/hifi/commerce/wallet/Wallet.qml | 4 +- .../qml/hifi/commerce/wallet/WalletChoice.qml | 4 +- .../qml/hifi/commerce/wallet/WalletHome.qml | 4 +- .../qml/hifi/commerce/wallet/WalletSetup.qml | 4 +- .../qml/hifi/dialogs/AboutDialog.qml | 2 +- .../qml/hifi/dialogs/RunningScripts.qml | 4 +- .../qml/hifi/dialogs/TabletAboutDialog.qml | 2 +- .../qml/hifi/dialogs/TabletAssetServer.qml | 4 +- .../qml/hifi/dialogs/TabletDCDialog.qml | 4 +- .../qml/hifi/dialogs/TabletDebugWindow.qml | 4 +- .../hifi/dialogs/TabletEntityStatistics.qml | 4 +- .../dialogs/TabletEntityStatisticsItem.qml | 4 +- .../qml/hifi/dialogs/TabletLODTools.qml | 4 +- .../qml/hifi/dialogs/TabletRunningScripts.qml | 4 +- .../dialogs/content/ModelBrowserContent.qml | 2 +- .../qml/hifi/tablet/CalibratingScreen.qml | 4 +- .../qml/hifi/tablet/ControllerSettings.qml | 4 +- .../qml/hifi/tablet/EditEntityList.qml | 4 +- .../qml/hifi/tablet/EditTabButton.qml | 4 +- .../resources/qml/hifi/tablet/EditTabView.qml | 4 +- .../qml/hifi/tablet/EditToolsTabView.qml | 4 +- .../qml/hifi/tablet/InputRecorder.qml | 4 +- .../qml/hifi/tablet/NewMaterialDialog.qml | 4 +- .../qml/hifi/tablet/NewModelDialog.qml | 4 +- .../qml/hifi/tablet/OpenVrConfiguration.qml | 4 +- .../qml/hifi/tablet/TabletAddressDialog.qml | 4 +- .../resources/qml/hifi/tablet/TabletHome.qml | 2 +- .../resources/qml/hifi/tablet/TabletMenu.qml | 2 +- .../qml/hifi/tablet/TabletMenuItem.qml | 4 +- .../qml/hifi/tablet/TabletMenuView.qml | 2 +- .../hifi/tablet/TabletModelBrowserDialog.qml | 4 +- .../tablet/tabletWindows/TabletFileDialog.qml | 4 +- .../tabletWindows/TabletPreferencesDialog.qml | 4 +- .../tabletWindows/preferences/Section.qml | 4 +- .../preferences/TabletBrowsablePreference.qml | 2 +- .../+android/HifiConstants.qml | 0 .../AnonymousProRegular.qml | 0 .../{stylesUit => styles-uit}/ButtonLabel.qml | 0 .../FiraSansRegular.qml | 0 .../FiraSansSemiBold.qml | 0 .../{stylesUit => styles-uit}/HiFiGlyphs.qml | 0 .../HifiConstants.qml | 0 .../{stylesUit => styles-uit}/IconButton.qml | 0 .../{stylesUit => styles-uit}/InfoItem.qml | 0 .../{stylesUit => styles-uit}/InputLabel.qml | 0 .../{stylesUit => styles-uit}/ListItem.qml | 0 .../qml/{stylesUit => styles-uit}/Logs.qml | 0 .../OverlayTitle.qml | 0 .../{stylesUit => styles-uit}/RalewayBold.qml | 0 .../RalewayLight.qml | 0 .../RalewayRegular.qml | 0 .../RalewaySemiBold.qml | 0 .../{stylesUit => styles-uit}/SectionName.qml | 0 .../{stylesUit => styles-uit}/Separator.qml | 2 +- .../ShortcutText.qml | 0 .../qml/{stylesUit => styles-uit}/TabName.qml | 0 .../TextFieldInput.qml | 0 .../qml/{stylesUit => styles-uit}/qmldir | 0 .../resources/qml/windows/Decoration.qml | 2 +- .../resources/qml/windows/DefaultFrame.qml | 2 +- .../qml/windows/DefaultFrameDecoration.qml | 2 +- interface/resources/qml/windows/Fadable.qml | 2 +- interface/resources/qml/windows/Frame.qml | 2 +- .../resources/qml/windows/ModalFrame.qml | 4 +- .../resources/qml/windows/ScrollingWindow.qml | 4 +- .../qml/windows/TabletModalFrame.qml | 4 +- interface/resources/qml/windows/ToolFrame.qml | 2 +- .../qml/windows/ToolFrameDecoration.qml | 2 +- interface/resources/qml/windows/Window.qml | 2 +- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 1 - scripts/developer/tests/ControlsGallery.qml | 11 +- scripts/developer/utilities/audio/Stats.qml | 2 +- .../developer/utilities/audio/TabletStats.qml | 3 +- .../utilities/lib/jet/qml/TaskList.qml | 4 +- .../utilities/lib/jet/qml/TaskListView.qml | 4 +- .../utilities/lib/plotperf/Color.qml | 4 +- .../utilities/render/antialiasing.qml | 4 +- .../render/configSlider/ConfigSlider.qml | 4 +- .../render/configSlider/RichSlider.qml | 4 +- .../utilities/render/deferredLighting.qml | 4 +- .../utilities/render/engineInspector.qml | 4 +- .../developer/utilities/render/highlight.qml | 4 +- .../render/highlight/HighlightStyle.qml | 4 +- scripts/developer/utilities/render/lod.qml | 4 +- scripts/developer/utilities/render/shadow.qml | 4 +- .../developer/utilities/render/transition.qml | 4 +- .../utilities/workload/workloadInspector.qml | 4 +- tests-manual/ui/qml/ControlsGalleryWindow.qml | 14 - tests-manual/ui/qml/Palettes.qml | 150 ++++ tests-manual/ui/qml/ScrollingGraph.qml | 111 +++ tests-manual/ui/qml/StubMenu.qml | 730 ++++++++++++++++++ tests-manual/ui/qml/Stubs.qml | 346 +++++++++ tests-manual/ui/qml/TestControllers.qml | 160 ++++ tests-manual/ui/qml/TestDialog.qml | 94 +++ tests-manual/ui/qml/TestMenu.qml | 10 + tests-manual/ui/qml/TestRoot.qml | 43 ++ .../ui/qml/controlDemo/ButtonPage.qml | 128 +++ tests-manual/ui/qml/controlDemo/InputPage.qml | 114 +++ .../ui/qml/controlDemo/ProgressPage.qml | 90 +++ tests-manual/ui/qml/controlDemo/main.qml | 161 ++++ tests-manual/ui/qml/main.qml | 461 +++++++++++ tests-manual/ui/qmlscratch.pro | 28 +- tests-manual/ui/src/main.cpp | 125 ++- .../spectator-camera/SpectatorCamera.qml | 4 +- 255 files changed, 3082 insertions(+), 389 deletions(-) rename interface/resources/qml/{controlsUit => controls-uit}/+android/ImageButton.qml (96%) rename interface/resources/qml/{controlsUit => controls-uit}/AttachmentsTable.qml (98%) rename interface/resources/qml/{controlsUit => controls-uit}/BaseWebView.qml (100%) rename interface/resources/qml/{controlsUit => controls-uit}/Button.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/CheckBox.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/CheckBoxQQC2.qml (98%) rename interface/resources/qml/{controlsUit => controls-uit}/ComboBox.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/ContentSection.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/FilterBar.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/GlyphButton.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/HorizontalRule.qml (100%) rename interface/resources/qml/{controlsUit => controls-uit}/HorizontalSpacer.qml (94%) rename interface/resources/qml/{controlsUit => controls-uit}/ImageMessageBox.qml (98%) rename interface/resources/qml/{controlsUit => controls-uit}/Key.qml (100%) rename interface/resources/qml/{controlsUit => controls-uit}/Keyboard.qml (100%) rename interface/resources/qml/{controlsUit => controls-uit}/Label.qml (97%) rename interface/resources/qml/{controlsUit => controls-uit}/QueuedButton.qml (98%) rename interface/resources/qml/{controlsUit => controls-uit}/RadioButton.qml (97%) rename interface/resources/qml/{controlsUit => controls-uit}/ScrollBar.qml (98%) rename interface/resources/qml/{controlsUit => controls-uit}/Separator.qml (98%) rename interface/resources/qml/{controlsUit => controls-uit}/Slider.qml (98%) rename interface/resources/qml/{controlsUit => controls-uit}/SpinBox.qml (98%) rename interface/resources/qml/{controlsUit => controls-uit}/Switch.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/Table.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/TabletContentSection.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/TabletHeader.qml (96%) rename interface/resources/qml/{controlsUit => controls-uit}/TextAction.qml (96%) rename interface/resources/qml/{controlsUit => controls-uit}/TextEdit.qml (95%) rename interface/resources/qml/{controlsUit => controls-uit}/TextField.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/ToolTip.qml (100%) rename interface/resources/qml/{controlsUit => controls-uit}/Tree.qml (99%) rename interface/resources/qml/{controlsUit => controls-uit}/VerticalSpacer.qml (94%) rename interface/resources/qml/{controlsUit => controls-uit}/WebGlyphButton.qml (98%) rename interface/resources/qml/{controlsUit => controls-uit}/WebSpinner.qml (100%) rename interface/resources/qml/{controlsUit => controls-uit}/WebView.qml (100%) rename interface/resources/qml/{controlsUit => controls-uit}/qmldir (100%) rename interface/resources/qml/{stylesUit => styles-uit}/+android/HifiConstants.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/AnonymousProRegular.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/ButtonLabel.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/FiraSansRegular.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/FiraSansSemiBold.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/HiFiGlyphs.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/HifiConstants.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/IconButton.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/InfoItem.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/InputLabel.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/ListItem.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/Logs.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/OverlayTitle.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/RalewayBold.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/RalewayLight.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/RalewayRegular.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/RalewaySemiBold.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/SectionName.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/Separator.qml (97%) rename interface/resources/qml/{stylesUit => styles-uit}/ShortcutText.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/TabName.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/TextFieldInput.qml (100%) rename interface/resources/qml/{stylesUit => styles-uit}/qmldir (100%) delete mode 100644 tests-manual/ui/qml/ControlsGalleryWindow.qml create mode 100644 tests-manual/ui/qml/Palettes.qml create mode 100644 tests-manual/ui/qml/ScrollingGraph.qml create mode 100644 tests-manual/ui/qml/StubMenu.qml create mode 100644 tests-manual/ui/qml/Stubs.qml create mode 100644 tests-manual/ui/qml/TestControllers.qml create mode 100644 tests-manual/ui/qml/TestDialog.qml create mode 100644 tests-manual/ui/qml/TestMenu.qml create mode 100644 tests-manual/ui/qml/TestRoot.qml create mode 100644 tests-manual/ui/qml/controlDemo/ButtonPage.qml create mode 100644 tests-manual/ui/qml/controlDemo/InputPage.qml create mode 100644 tests-manual/ui/qml/controlDemo/ProgressPage.qml create mode 100644 tests-manual/ui/qml/controlDemo/main.qml create mode 100644 tests-manual/ui/qml/main.qml diff --git a/interface/resources/QtWebEngine/UIDelegates/Menu.qml b/interface/resources/QtWebEngine/UIDelegates/Menu.qml index adfd29df9e..46c00e758e 100644 --- a/interface/resources/QtWebEngine/UIDelegates/Menu.qml +++ b/interface/resources/QtWebEngine/UIDelegates/Menu.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 -import controlsUit 1.0 -import stylesUit 1.0 +import "../../qml/controls-uit" +import "../../qml/styles-uit" Item { id: menu diff --git a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml index b4d3ca4bb2..6014b6834b 100644 --- a/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml +++ b/interface/resources/QtWebEngine/UIDelegates/MenuItem.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 -import controlsUit 1.0 -import stylesUit 1.0 +import "../../qml/controls-uit" +import "../../qml/styles-uit" Item { id: root diff --git a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml index 089c745571..e4ab3037ef 100644 --- a/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml +++ b/interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 -import controlsUit 1.0 -import stylesUit 1.0 +import "../../qml/controls-uit" +import "../../qml/styles-uit" import "../../qml/dialogs" QtObject { diff --git a/interface/resources/qml/AudioScopeUI.qml b/interface/resources/qml/AudioScopeUI.qml index 91908807e2..aa181dbf8d 100644 --- a/interface/resources/qml/AudioScopeUI.qml +++ b/interface/resources/qml/AudioScopeUI.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "styles-uit" +import "controls-uit" as HifiControlsUit Item { id: root diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 01de7a36f9..4474cfb2cd 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -2,9 +2,9 @@ import QtQuick 2.5 import QtWebChannel 1.0 import QtWebEngine 1.5 -import controlsUit 1.0 +import "controls-uit" import "styles" as HifiStyles -import stylesUit 1.0 +import "styles-uit" import "windows" ScrollingWindow { diff --git a/interface/resources/qml/CurrentAPI.qml b/interface/resources/qml/CurrentAPI.qml index 4ea45041c3..96bfb5c36b 100644 --- a/interface/resources/qml/CurrentAPI.qml +++ b/interface/resources/qml/CurrentAPI.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "styles-uit" +import "controls-uit" as HifiControls Item { id: root diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index 8c5900b4c3..f18969fb2f 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import Hifi 1.0 as Hifi -import controlsUit 1.0 +import "controls-uit" import "windows" as Windows Windows.ScrollingWindow { diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml index c217238e93..e8ddbf823d 100644 --- a/interface/resources/qml/InteractiveWindow.qml +++ b/interface/resources/qml/InteractiveWindow.qml @@ -12,9 +12,9 @@ import QtQuick 2.3 import "windows" as Windows import "controls" -import controlsUit 1.0 as Controls +import "controls-uit" as Controls import "styles" -import stylesUit 1.0 +import "styles-uit" Windows.Window { id: root; diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 12117aaba4..336858502d 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -11,8 +11,8 @@ import Hifi 1.0 import QtQuick 2.4 -import controlsUit 1.0 -import stylesUit 1.0 +import "controls-uit" +import "styles-uit" import "windows" import "LoginDialog" diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index a40110b1e9..96b638c911 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -13,8 +13,8 @@ import QtQuick 2.4 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import controlsUit 1.0 -import stylesUit 1.0 +import "../../controls-uit" +import "../../styles-uit" Item { id: linkAccountBody diff --git a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml index 10909e4c85..3a44a8d741 100644 --- a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/+android/SignUpBody.qml @@ -13,8 +13,8 @@ import QtQuick 2.4 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import controlsUit 1.0 -import stylesUit 1.0 +import "../../controls-uit" +import "../../styles-uit" Item { id: signupBody diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml index 3a57061de4..fe4c511f1d 100644 --- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml +++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" Item { id: completeProfileBody diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 7951d45c0e..48cf124127 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -13,9 +13,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import controlsUit 1.0 -import stylesUit 1.0 - +import "../controls-uit" +import "../styles-uit" Item { id: linkAccountBody clip: true diff --git a/interface/resources/qml/LoginDialog/SignInBody.qml b/interface/resources/qml/LoginDialog/SignInBody.qml index 7fe29e13f6..9cb1add704 100644 --- a/interface/resources/qml/LoginDialog/SignInBody.qml +++ b/interface/resources/qml/LoginDialog/SignInBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls.Styles 1.4 as OriginalStyles -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" Item { id: signInBody diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index d3c898d76f..bb30696e4c 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls 1.4 -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" Item { id: signupBody diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index 2a41353534..bf05a36ce1 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -12,8 +12,8 @@ import Hifi 1.0 import QtQuick 2.4 import QtQuick.Controls 1.4 -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" Item { id: usernameCollisionBody diff --git a/interface/resources/qml/LoginDialog/WelcomeBody.qml b/interface/resources/qml/LoginDialog/WelcomeBody.qml index 020e6db002..551ec263b7 100644 --- a/interface/resources/qml/LoginDialog/WelcomeBody.qml +++ b/interface/resources/qml/LoginDialog/WelcomeBody.qml @@ -11,8 +11,8 @@ import Hifi 1.0 import QtQuick 2.4 -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" Item { id: welcomeBody diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 322535641d..8c4d6145ec 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -13,8 +13,8 @@ import QtWebEngine 1.1 import QtWebChannel 1.0 import "windows" as Windows -import controlsUit 1.0 as Controls -import stylesUit 1.0 +import "controls-uit" as Controls +import "styles-uit" Windows.ScrollingWindow { id: root diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 53e6bcc37d..bef6423e25 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -2,9 +2,9 @@ import QtQuick 2.3 import "windows" as Windows import "controls" -import controlsUit 1.0 as Controls +import "controls-uit" as Controls import "styles" -import stylesUit 1.0 +import "styles-uit" Windows.Window { id: root diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index 720a904231..141c1f25a7 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -3,9 +3,9 @@ import QtWebChannel 1.0 import QtWebEngine 1.5 import "controls" -import controlsUit 1.0 as HifiControls +import "controls-uit" as HifiControls import "styles" as HifiStyles -import stylesUit 1.0 +import "styles-uit" import "windows" Item { diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index 9c22d0b65b..5e05601ce4 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -4,9 +4,9 @@ import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtGraphicalEffects 1.0 -import controlsUit 1.0 +import "controls-uit" import "styles" as HifiStyles -import stylesUit 1.0 +import "styles-uit" import "windows" ScrollingWindow { diff --git a/interface/resources/qml/controlsUit/+android/ImageButton.qml b/interface/resources/qml/controls-uit/+android/ImageButton.qml similarity index 96% rename from interface/resources/qml/controlsUit/+android/ImageButton.qml rename to interface/resources/qml/controls-uit/+android/ImageButton.qml index 88eaf95d76..5ebf7cd3e9 100644 --- a/interface/resources/qml/controlsUit/+android/ImageButton.qml +++ b/interface/resources/qml/controls-uit/+android/ImageButton.qml @@ -1,6 +1,6 @@ // // ImageButton.qml -// interface/resources/qml/controlsUit +// interface/resources/qml/controls-uit // // Created by Gabriel Calero & Cristian Duarte on 12 Oct 2017 // Copyright 2017 High Fidelity, Inc. @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Layouts 1.3 -import "../stylesUit" as HifiStyles +import "../styles-uit" as HifiStyles Item { id: button @@ -79,4 +79,4 @@ Item { } } ] -} +} \ No newline at end of file diff --git a/interface/resources/qml/controlsUit/AttachmentsTable.qml b/interface/resources/qml/controls-uit/AttachmentsTable.qml similarity index 98% rename from interface/resources/qml/controlsUit/AttachmentsTable.qml rename to interface/resources/qml/controls-uit/AttachmentsTable.qml index a2677962da..8ee9909ab8 100644 --- a/interface/resources/qml/controlsUit/AttachmentsTable.qml +++ b/interface/resources/qml/controls-uit/AttachmentsTable.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.XmlListModel 2.0 -import "../stylesUit" -import "." as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls import "../windows" import "../hifi/models" diff --git a/interface/resources/qml/controlsUit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml similarity index 100% rename from interface/resources/qml/controlsUit/BaseWebView.qml rename to interface/resources/qml/controls-uit/BaseWebView.qml diff --git a/interface/resources/qml/controlsUit/Button.qml b/interface/resources/qml/controls-uit/Button.qml similarity index 99% rename from interface/resources/qml/controlsUit/Button.qml rename to interface/resources/qml/controls-uit/Button.qml index 6ea7ce4b4c..f1a6e4bb4a 100644 --- a/interface/resources/qml/controlsUit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -12,7 +12,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 as Original import TabletScriptingInterface 1.0 -import "../stylesUit" +import "../styles-uit" Original.Button { id: control; diff --git a/interface/resources/qml/controlsUit/CheckBox.qml b/interface/resources/qml/controls-uit/CheckBox.qml similarity index 99% rename from interface/resources/qml/controlsUit/CheckBox.qml rename to interface/resources/qml/controls-uit/CheckBox.qml index abf08908fb..6e4a3df010 100644 --- a/interface/resources/qml/controlsUit/CheckBox.qml +++ b/interface/resources/qml/controls-uit/CheckBox.qml @@ -11,7 +11,7 @@ import QtQuick 2.2 import QtQuick.Controls 2.2 as Original -import "../stylesUit" +import "../styles-uit" import TabletScriptingInterface 1.0 diff --git a/interface/resources/qml/controlsUit/CheckBoxQQC2.qml b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml similarity index 98% rename from interface/resources/qml/controlsUit/CheckBoxQQC2.qml rename to interface/resources/qml/controls-uit/CheckBoxQQC2.qml index 91d35ecd58..8a9686ff5e 100644 --- a/interface/resources/qml/controlsUit/CheckBoxQQC2.qml +++ b/interface/resources/qml/controls-uit/CheckBoxQQC2.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../stylesUit" -import "." as HiFiControls +import "../styles-uit" +import "../controls-uit" as HiFiControls import TabletScriptingInterface 1.0 CheckBox { diff --git a/interface/resources/qml/controlsUit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml similarity index 99% rename from interface/resources/qml/controlsUit/ComboBox.qml rename to interface/resources/qml/controls-uit/ComboBox.qml index 8d1d7a5262..245b565a62 100644 --- a/interface/resources/qml/controlsUit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../stylesUit" -import "." as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls FocusScope { id: root diff --git a/interface/resources/qml/controlsUit/ContentSection.qml b/interface/resources/qml/controls-uit/ContentSection.qml similarity index 99% rename from interface/resources/qml/controlsUit/ContentSection.qml rename to interface/resources/qml/controls-uit/ContentSection.qml index 262c29220f..47a13e9262 100644 --- a/interface/resources/qml/controlsUit/ContentSection.qml +++ b/interface/resources/qml/controls-uit/ContentSection.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../stylesUit" +import "../styles-uit" Column { property string name: "Content Section" diff --git a/interface/resources/qml/controlsUit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml similarity index 99% rename from interface/resources/qml/controlsUit/FilterBar.qml rename to interface/resources/qml/controls-uit/FilterBar.qml index 3e407040bc..ecae790b22 100644 --- a/interface/resources/qml/controlsUit/FilterBar.qml +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -12,8 +12,8 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import "../stylesUit" -import "." as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls Item { id: root; diff --git a/interface/resources/qml/controlsUit/GlyphButton.qml b/interface/resources/qml/controls-uit/GlyphButton.qml similarity index 99% rename from interface/resources/qml/controlsUit/GlyphButton.qml rename to interface/resources/qml/controls-uit/GlyphButton.qml index 17f7fba2d6..9129486720 100644 --- a/interface/resources/qml/controlsUit/GlyphButton.qml +++ b/interface/resources/qml/controls-uit/GlyphButton.qml @@ -12,7 +12,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 as Original import TabletScriptingInterface 1.0 -import "../stylesUit" +import "../styles-uit" Original.Button { id: control diff --git a/interface/resources/qml/controlsUit/HorizontalRule.qml b/interface/resources/qml/controls-uit/HorizontalRule.qml similarity index 100% rename from interface/resources/qml/controlsUit/HorizontalRule.qml rename to interface/resources/qml/controls-uit/HorizontalRule.qml diff --git a/interface/resources/qml/controlsUit/HorizontalSpacer.qml b/interface/resources/qml/controls-uit/HorizontalSpacer.qml similarity index 94% rename from interface/resources/qml/controlsUit/HorizontalSpacer.qml rename to interface/resources/qml/controls-uit/HorizontalSpacer.qml index efcabf2699..545154ab44 100644 --- a/interface/resources/qml/controlsUit/HorizontalSpacer.qml +++ b/interface/resources/qml/controls-uit/HorizontalSpacer.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../stylesUit" +import "../styles-uit" Item { id: root diff --git a/interface/resources/qml/controlsUit/ImageMessageBox.qml b/interface/resources/qml/controls-uit/ImageMessageBox.qml similarity index 98% rename from interface/resources/qml/controlsUit/ImageMessageBox.qml rename to interface/resources/qml/controls-uit/ImageMessageBox.qml index 46d93383a4..74313f7ffe 100644 --- a/interface/resources/qml/controlsUit/ImageMessageBox.qml +++ b/interface/resources/qml/controls-uit/ImageMessageBox.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "../stylesUit" +import "../styles-uit" Item { id: imageBox diff --git a/interface/resources/qml/controlsUit/Key.qml b/interface/resources/qml/controls-uit/Key.qml similarity index 100% rename from interface/resources/qml/controlsUit/Key.qml rename to interface/resources/qml/controls-uit/Key.qml diff --git a/interface/resources/qml/controlsUit/Keyboard.qml b/interface/resources/qml/controls-uit/Keyboard.qml similarity index 100% rename from interface/resources/qml/controlsUit/Keyboard.qml rename to interface/resources/qml/controls-uit/Keyboard.qml diff --git a/interface/resources/qml/controlsUit/Label.qml b/interface/resources/qml/controls-uit/Label.qml similarity index 97% rename from interface/resources/qml/controlsUit/Label.qml rename to interface/resources/qml/controls-uit/Label.qml index 7f208cde88..4c7051b495 100644 --- a/interface/resources/qml/controlsUit/Label.qml +++ b/interface/resources/qml/controls-uit/Label.qml @@ -10,7 +10,7 @@ import QtQuick 2.7 -import "../stylesUit" +import "../styles-uit" RalewaySemiBold { HifiConstants { id: hifi } diff --git a/interface/resources/qml/controlsUit/QueuedButton.qml b/interface/resources/qml/controls-uit/QueuedButton.qml similarity index 98% rename from interface/resources/qml/controlsUit/QueuedButton.qml rename to interface/resources/qml/controls-uit/QueuedButton.qml index 70ad9eb112..6612d582df 100644 --- a/interface/resources/qml/controlsUit/QueuedButton.qml +++ b/interface/resources/qml/controls-uit/QueuedButton.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../stylesUit" +import "../styles-uit" import "." as HifiControls HifiControls.Button { diff --git a/interface/resources/qml/controlsUit/RadioButton.qml b/interface/resources/qml/controls-uit/RadioButton.qml similarity index 97% rename from interface/resources/qml/controlsUit/RadioButton.qml rename to interface/resources/qml/controls-uit/RadioButton.qml index ad62a77aa7..56324c55d7 100644 --- a/interface/resources/qml/controlsUit/RadioButton.qml +++ b/interface/resources/qml/controls-uit/RadioButton.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 as Original -import "../stylesUit" -import "." as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls import TabletScriptingInterface 1.0 diff --git a/interface/resources/qml/controlsUit/ScrollBar.qml b/interface/resources/qml/controls-uit/ScrollBar.qml similarity index 98% rename from interface/resources/qml/controlsUit/ScrollBar.qml rename to interface/resources/qml/controls-uit/ScrollBar.qml index bcb1f62429..125e84e585 100644 --- a/interface/resources/qml/controlsUit/ScrollBar.qml +++ b/interface/resources/qml/controls-uit/ScrollBar.qml @@ -11,7 +11,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../stylesUit" +import "../styles-uit" ScrollBar { visible: size < 1.0 diff --git a/interface/resources/qml/controlsUit/Separator.qml b/interface/resources/qml/controls-uit/Separator.qml similarity index 98% rename from interface/resources/qml/controlsUit/Separator.qml rename to interface/resources/qml/controls-uit/Separator.qml index da6b9adf57..3350764ae9 100644 --- a/interface/resources/qml/controlsUit/Separator.qml +++ b/interface/resources/qml/controls-uit/Separator.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "../stylesUit" +import "../styles-uit" Item { property int colorScheme: 0; diff --git a/interface/resources/qml/controlsUit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml similarity index 98% rename from interface/resources/qml/controlsUit/Slider.qml rename to interface/resources/qml/controls-uit/Slider.qml index 8cb08b69e2..2a5d4c137d 100644 --- a/interface/resources/qml/controlsUit/Slider.qml +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../stylesUit" -import "." as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls Slider { id: slider diff --git a/interface/resources/qml/controlsUit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml similarity index 98% rename from interface/resources/qml/controlsUit/SpinBox.qml rename to interface/resources/qml/controls-uit/SpinBox.qml index d24c7c5e8c..3d3ea7a75e 100644 --- a/interface/resources/qml/controlsUit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import "../stylesUit" -import "." as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls SpinBox { id: spinBox diff --git a/interface/resources/qml/controlsUit/Switch.qml b/interface/resources/qml/controls-uit/Switch.qml similarity index 99% rename from interface/resources/qml/controlsUit/Switch.qml rename to interface/resources/qml/controls-uit/Switch.qml index 0961ef2500..bfe86b1420 100644 --- a/interface/resources/qml/controlsUit/Switch.qml +++ b/interface/resources/qml/controls-uit/Switch.qml @@ -11,7 +11,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 as Original -import "../stylesUit" +import "../styles-uit" Item { id: rootSwitch; diff --git a/interface/resources/qml/controlsUit/Table.qml b/interface/resources/qml/controls-uit/Table.qml similarity index 99% rename from interface/resources/qml/controlsUit/Table.qml rename to interface/resources/qml/controls-uit/Table.qml index ab74361046..ce4e1c376a 100644 --- a/interface/resources/qml/controlsUit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -13,7 +13,7 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Controls 2.3 as QQC2 -import "../stylesUit" +import "../styles-uit" TableView { id: tableView diff --git a/interface/resources/qml/controlsUit/TabletContentSection.qml b/interface/resources/qml/controls-uit/TabletContentSection.qml similarity index 99% rename from interface/resources/qml/controlsUit/TabletContentSection.qml rename to interface/resources/qml/controls-uit/TabletContentSection.qml index dccaf31bbe..c34f4afdd6 100644 --- a/interface/resources/qml/controlsUit/TabletContentSection.qml +++ b/interface/resources/qml/controls-uit/TabletContentSection.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../stylesUit" +import "../styles-uit" Column { property string name: "Content Section" diff --git a/interface/resources/qml/controlsUit/TabletHeader.qml b/interface/resources/qml/controls-uit/TabletHeader.qml similarity index 96% rename from interface/resources/qml/controlsUit/TabletHeader.qml rename to interface/resources/qml/controls-uit/TabletHeader.qml index f626700742..56203de286 100644 --- a/interface/resources/qml/controlsUit/TabletHeader.qml +++ b/interface/resources/qml/controls-uit/TabletHeader.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../stylesUit" +import "../styles-uit" Rectangle { diff --git a/interface/resources/qml/controlsUit/TextAction.qml b/interface/resources/qml/controls-uit/TextAction.qml similarity index 96% rename from interface/resources/qml/controlsUit/TextAction.qml rename to interface/resources/qml/controls-uit/TextAction.qml index a0a1bb7d07..1745a6c273 100644 --- a/interface/resources/qml/controlsUit/TextAction.qml +++ b/interface/resources/qml/controls-uit/TextAction.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import "../stylesUit" -import "." as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls Item { property string icon: "" diff --git a/interface/resources/qml/controlsUit/TextEdit.qml b/interface/resources/qml/controls-uit/TextEdit.qml similarity index 95% rename from interface/resources/qml/controlsUit/TextEdit.qml rename to interface/resources/qml/controls-uit/TextEdit.qml index 7446c5040f..a72a3b13d8 100644 --- a/interface/resources/qml/controlsUit/TextEdit.qml +++ b/interface/resources/qml/controls-uit/TextEdit.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "../stylesUit" +import "../styles-uit" TextEdit { diff --git a/interface/resources/qml/controlsUit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml similarity index 99% rename from interface/resources/qml/controlsUit/TextField.qml rename to interface/resources/qml/controls-uit/TextField.qml index d78f3a1340..917068ac01 100644 --- a/interface/resources/qml/controlsUit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 -import "../stylesUit" -import "." as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls TextField { id: textField diff --git a/interface/resources/qml/controlsUit/ToolTip.qml b/interface/resources/qml/controls-uit/ToolTip.qml similarity index 100% rename from interface/resources/qml/controlsUit/ToolTip.qml rename to interface/resources/qml/controls-uit/ToolTip.qml diff --git a/interface/resources/qml/controlsUit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml similarity index 99% rename from interface/resources/qml/controlsUit/Tree.qml rename to interface/resources/qml/controls-uit/Tree.qml index f2c49095b1..5199a10a27 100644 --- a/interface/resources/qml/controlsUit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -15,7 +15,7 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Controls 2.2 as QQC2 -import "../stylesUit" +import "../styles-uit" TreeView { id: treeView diff --git a/interface/resources/qml/controlsUit/VerticalSpacer.qml b/interface/resources/qml/controls-uit/VerticalSpacer.qml similarity index 94% rename from interface/resources/qml/controlsUit/VerticalSpacer.qml rename to interface/resources/qml/controls-uit/VerticalSpacer.qml index 4c93aa1801..2df65f1002 100644 --- a/interface/resources/qml/controlsUit/VerticalSpacer.qml +++ b/interface/resources/qml/controls-uit/VerticalSpacer.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import "../stylesUit" +import "../styles-uit" Item { id: root diff --git a/interface/resources/qml/controlsUit/WebGlyphButton.qml b/interface/resources/qml/controls-uit/WebGlyphButton.qml similarity index 98% rename from interface/resources/qml/controlsUit/WebGlyphButton.qml rename to interface/resources/qml/controls-uit/WebGlyphButton.qml index 7739ecd5e7..fd7cd001b2 100644 --- a/interface/resources/qml/controlsUit/WebGlyphButton.qml +++ b/interface/resources/qml/controls-uit/WebGlyphButton.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 as Original -import "../stylesUit" +import "../styles-uit" Original.Button { id: control diff --git a/interface/resources/qml/controlsUit/WebSpinner.qml b/interface/resources/qml/controls-uit/WebSpinner.qml similarity index 100% rename from interface/resources/qml/controlsUit/WebSpinner.qml rename to interface/resources/qml/controls-uit/WebSpinner.qml diff --git a/interface/resources/qml/controlsUit/WebView.qml b/interface/resources/qml/controls-uit/WebView.qml similarity index 100% rename from interface/resources/qml/controlsUit/WebView.qml rename to interface/resources/qml/controls-uit/WebView.qml diff --git a/interface/resources/qml/controlsUit/qmldir b/interface/resources/qml/controls-uit/qmldir similarity index 100% rename from interface/resources/qml/controlsUit/qmldir rename to interface/resources/qml/controls-uit/qmldir diff --git a/interface/resources/qml/controls/Button.qml b/interface/resources/qml/controls/Button.qml index b677822c0e..6cbdec5644 100644 --- a/interface/resources/qml/controls/Button.qml +++ b/interface/resources/qml/controls/Button.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.2 as Original import "." import "../styles" +import "../controls-uit" Original.Button { id: control diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index cce32c137a..943f15e1de 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -4,7 +4,7 @@ import QtWebChannel 1.0 import QtQuick.Controls 2.2 -import stylesUit 1.0 as StylesUIt +import "../styles-uit" as StylesUIt Item { id: flick diff --git a/interface/resources/qml/controls/TabletWebButton.qml b/interface/resources/qml/controls/TabletWebButton.qml index 140461d817..d016f71f2d 100644 --- a/interface/resources/qml/controls/TabletWebButton.qml +++ b/interface/resources/qml/controls/TabletWebButton.qml @@ -10,7 +10,7 @@ import Hifi 1.0 import QtQuick 2.4 -import stylesUit 1.0 +import "../styles-uit" Rectangle { property alias text: label.text diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml index be11f16498..bb037ad478 100644 --- a/interface/resources/qml/controls/TabletWebScreen.qml +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -1,5 +1,5 @@ import QtQuick 2.7 -import controlsUit 1.0 as HiFiControls +import "../controls-uit" as HiFiControls Item { id: root diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 0c5ca37e00..db695dbfb2 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -1,8 +1,8 @@ import QtQuick 2.7 import QtWebEngine 1.5 -import controlsUit 1.0 as HiFiControls +import "../controls-uit" as HiFiControls import "../styles" as HifiStyles -import stylesUit 1.0 +import "../styles-uit" Item { id: root diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 375bcd50e0..71bf69fdc8 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -1,5 +1,5 @@ import QtQuick 2.7 -import controlsUit 1.0 as HiFiControls +import "../controls-uit" as HiFiControls Item { width: parent !== null ? parent.width : undefined diff --git a/interface/resources/qml/dialogs/AssetDialog.qml b/interface/resources/qml/dialogs/AssetDialog.qml index b8eaab0b8d..e8d28e9b37 100644 --- a/interface/resources/qml/dialogs/AssetDialog.qml +++ b/interface/resources/qml/dialogs/AssetDialog.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import Qt.labs.settings 1.0 -import stylesUit 1.0 +import "../styles-uit" import "../windows" import "assetDialog" diff --git a/interface/resources/qml/dialogs/CustomQueryDialog.qml b/interface/resources/qml/dialogs/CustomQueryDialog.qml index 026068eee1..0c86b93c4b 100644 --- a/interface/resources/qml/dialogs/CustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/CustomQueryDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.7; import QtQuick.Dialogs 1.2 as OriginalDialogs; import QtQuick.Controls 2.3 -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit"; +import "../styles-uit"; import "../windows"; ModalWindow { diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index b7340575dd..6651af0db3 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -16,8 +16,8 @@ import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" import "../windows" import "fileDialog" diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 9428e3ab6e..b5ac6cab72 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 as OriginalDialogs -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" import "../windows" import "messageDialog" diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index 9df1d0b963..fffd0e2ed9 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import controlsUit 1.0 as HifiControls -import stylesUit 1.0 +import "../controls-uit" as HifiControls +import "../styles-uit" import "../windows" import "preferences" diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index 9cfb3011bd..41ee30e6d5 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" import "../windows" ModalWindow { diff --git a/interface/resources/qml/dialogs/TabletAssetDialog.qml b/interface/resources/qml/dialogs/TabletAssetDialog.qml index b3bd45f972..897378e40c 100644 --- a/interface/resources/qml/dialogs/TabletAssetDialog.qml +++ b/interface/resources/qml/dialogs/TabletAssetDialog.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import stylesUit 1.0 +import "../styles-uit" import "../windows" import "assetDialog" diff --git a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml index c7772984ab..81a2c5c1e0 100644 --- a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 2.3 -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" import "../windows" TabletModalWindow { diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 3be6e30dd0..6848c230e3 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -16,8 +16,8 @@ import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" import "../windows" import "fileDialog" diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index 6314921286..c85b2b2ba0 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -11,8 +11,8 @@ import Hifi 1.0 import QtQuick 2.5 -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" import "../windows" import "../LoginDialog" diff --git a/interface/resources/qml/dialogs/TabletMessageBox.qml b/interface/resources/qml/dialogs/TabletMessageBox.qml index 1e6f0734ad..fabe0dd247 100644 --- a/interface/resources/qml/dialogs/TabletMessageBox.qml +++ b/interface/resources/qml/dialogs/TabletMessageBox.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 as OriginalDialogs -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" import "../windows" import "messageDialog" diff --git a/interface/resources/qml/dialogs/TabletQueryDialog.qml b/interface/resources/qml/dialogs/TabletQueryDialog.qml index 8f63730b8e..5746a3d67c 100644 --- a/interface/resources/qml/dialogs/TabletQueryDialog.qml +++ b/interface/resources/qml/dialogs/TabletQueryDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Dialogs 1.2 as OriginalDialogs import QtQuick.Controls 2.3 -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" import "../windows" TabletModalWindow { diff --git a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml index da976ef3e1..c3e842bc2f 100644 --- a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml +++ b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 import QtQuick.Controls 1.5 as QQC1 -import controlsUit 1.0 -import stylesUit 1.0 +import "../../controls-uit" +import "../../styles-uit" import "../fileDialog" diff --git a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml index 6c042b5598..50a10974b5 100644 --- a/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml +++ b/interface/resources/qml/dialogs/fileDialog/FileTypeSelection.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import controlsUit 1.0 +import "../../controls-uit" ComboBox { id: root diff --git a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml index f5715fa2c2..8411980db7 100644 --- a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml +++ b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 -import controlsUit 1.0 +import "../../controls-uit" Button { property var dialog; diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml index 9505e70530..0efc3776b3 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import controlsUit 1.0 +import "../../controls-uit" import "../../hifi/tablet/tabletWindows/preferences" Preference { diff --git a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml index 6059f8ff1c..2cf50891c9 100644 --- a/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/BrowsablePreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import controlsUit 1.0 +import "../../controls-uit" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml index 09c5b4329d..454a9124ae 100644 --- a/interface/resources/qml/dialogs/preferences/ButtonPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ButtonPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import TabletScriptingInterface 1.0 -import controlsUit 1.0 +import "../../controls-uit" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index f6f840bbe8..e2172d8eda 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import TabletScriptingInterface 1.0 -import controlsUit 1.0 +import "../../controls-uit" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml index 98cb397976..3b3efaf520 100644 --- a/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/ComboBoxPreference.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import controlsUit 1.0 as HiFiControls -import stylesUit 1.0 +import "../../controls-uit" as HiFiControls +import "../../styles-uit" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/EditablePreference.qml b/interface/resources/qml/dialogs/preferences/EditablePreference.qml index e0c79ebba0..8acf8e1f76 100644 --- a/interface/resources/qml/dialogs/preferences/EditablePreference.qml +++ b/interface/resources/qml/dialogs/preferences/EditablePreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import controlsUit 1.0 +import "../../controls-uit" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml b/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml index f963003c59..cfc2e94ed9 100644 --- a/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml +++ b/interface/resources/qml/dialogs/preferences/PrimaryHandPreference.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import controlsUit 1.0 +import "../../controls-uit" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml index 0a09d8d609..103904a666 100644 --- a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml +++ b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import controlsUit 1.0 -import stylesUit 1.0 +import "../../controls-uit" +import "../../styles-uit" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml index a9b755ad83..c2c6583b7e 100644 --- a/interface/resources/qml/dialogs/preferences/Section.qml +++ b/interface/resources/qml/dialogs/preferences/Section.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import Hifi 1.0 -import controlsUit 1.0 as HiFiControls -import stylesUit 1.0 +import "../../controls-uit" as HiFiControls +import "../../styles-uit" import "." Preference { diff --git a/interface/resources/qml/dialogs/preferences/SliderPreference.qml b/interface/resources/qml/dialogs/preferences/SliderPreference.qml index c8a2aae158..2bdda09fc3 100644 --- a/interface/resources/qml/dialogs/preferences/SliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SliderPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import controlsUit 1.0 +import "../../controls-uit" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml index 1b080c2759..b2c334b674 100644 --- a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import controlsUit 1.0 +import "../../controls-uit" Preference { id: root diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index cbc804d9d7..126e62fc30 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../dialogs" -import controlsUit 1.0 +import "../../controls-uit" Preference { id: root diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android/ActionBar.qml index 3c58156f30..d487901d6f 100644 --- a/interface/resources/qml/hifi/+android/ActionBar.qml +++ b/interface/resources/qml/hifi/+android/ActionBar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android/AudioBar.qml index 912572fdf8..6cc17fccf7 100644 --- a/interface/resources/qml/hifi/+android/AudioBar.qml +++ b/interface/resources/qml/hifi/+android/AudioBar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/AvatarOption.qml b/interface/resources/qml/hifi/+android/AvatarOption.qml index 7eba3c2a67..85d7e52eb2 100644 --- a/interface/resources/qml/hifi/+android/AvatarOption.qml +++ b/interface/resources/qml/hifi/+android/AvatarOption.qml @@ -11,7 +11,7 @@ import QtQuick.Layouts 1.3 import QtQuick 2.5 -import controlsUit 1.0 as HifiControlsUit +import "../controls-uit" as HifiControlsUit ColumnLayout { id: itemRoot diff --git a/interface/resources/qml/hifi/+android/StatsBar.qml b/interface/resources/qml/hifi/+android/StatsBar.qml index 64e93b4a08..aee438b44f 100644 --- a/interface/resources/qml/hifi/+android/StatsBar.qml +++ b/interface/resources/qml/hifi/+android/StatsBar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/WindowHeader.qml b/interface/resources/qml/hifi/+android/WindowHeader.qml index 5316fc4786..4ec0a0c6e6 100644 --- a/interface/resources/qml/hifi/+android/WindowHeader.qml +++ b/interface/resources/qml/hifi/+android/WindowHeader.qml @@ -16,8 +16,8 @@ import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 import "." import "../styles" as HifiStyles -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../styles-uit" +import "../controls-uit" as HifiControlsUit import "../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/+android/bottomHudOptions.qml b/interface/resources/qml/hifi/+android/bottomHudOptions.qml index 6b830d94c2..22beccf531 100644 --- a/interface/resources/qml/hifi/+android/bottomHudOptions.qml +++ b/interface/resources/qml/hifi/+android/bottomHudOptions.qml @@ -16,8 +16,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 import "../../styles" as HifiStyles -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls import ".." import "." diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index 1bf04fb8d9..994bf1efe4 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls import ".." diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index ad337a6361..1a7f5bac40 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -14,8 +14,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls import "../windows" as Windows import "../dialogs" diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 39590748cf..aea5931627 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -3,8 +3,8 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtQml.Models 2.1 import QtGraphicalEffects 1.0 -import controlsUit 1.0 as HifiControls -import stylesUit 1.0 +import "../controls-uit" as HifiControls +import "../styles-uit" import "avatarapp" Rectangle { diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 7f29324416..83bf1e2c54 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -17,7 +17,7 @@ import QtGraphicalEffects 1.0 import TabletScriptingInterface 1.0 import "toolbars" -import stylesUit 1.0 +import "../styles-uit" Item { id: root; diff --git a/interface/resources/qml/hifi/ComboDialog.qml b/interface/resources/qml/hifi/ComboDialog.qml index 74d9c1019b..e5dc8a9c1a 100644 --- a/interface/resources/qml/hifi/ComboDialog.qml +++ b/interface/resources/qml/hifi/ComboDialog.qml @@ -10,8 +10,8 @@ // import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 +import "../styles-uit" +import "../controls-uit" Item { property var dialogTitleText : ""; diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 511d9377e5..4d342fe775 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -8,7 +8,7 @@ import "../desktop" as OriginalDesktop import ".." import "." import "./toolbars" -import controlsUit 1.0 +import "../controls-uit" OriginalDesktop.Desktop { id: desktop diff --git a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml index 048add24e5..9e9dcc75b2 100644 --- a/interface/resources/qml/hifi/DesktopLetterboxMessage.qml +++ b/interface/resources/qml/hifi/DesktopLetterboxMessage.qml @@ -10,7 +10,7 @@ // import QtQuick 2.5 -import stylesUit 1.0 +import "../styles-uit" Item { property alias text: popupText.text diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 4cfd4804b3..346481fe1f 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -15,7 +15,7 @@ import Hifi 1.0 import QtQuick 2.5 import QtGraphicalEffects 1.0 import "toolbars" -import stylesUit 1.0 +import "../styles-uit" import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. Column { diff --git a/interface/resources/qml/hifi/LetterboxMessage.qml b/interface/resources/qml/hifi/LetterboxMessage.qml index 68bebdd041..8a18d88842 100644 --- a/interface/resources/qml/hifi/LetterboxMessage.qml +++ b/interface/resources/qml/hifi/LetterboxMessage.qml @@ -10,7 +10,7 @@ // import QtQuick 2.5 -import stylesUit 1.0 +import "../styles-uit" Item { property alias text: popupText.text diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 242ca5ab57..dfa6555150 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -13,8 +13,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls import "toolbars" // references Users, UserActivityLogger, MyAvatar, Vec3, Quat, AddressManager, Account from root context diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 368beaab47..1384cb8711 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -15,8 +15,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../styles-uit" +import "../controls-uit" as HifiControlsUit import "../controls" as HifiControls import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. diff --git a/interface/resources/qml/hifi/SkyboxChanger.qml b/interface/resources/qml/hifi/SkyboxChanger.qml index a66fc38415..f0c97a11a3 100644 --- a/interface/resources/qml/hifi/SkyboxChanger.qml +++ b/interface/resources/qml/hifi/SkyboxChanger.qml @@ -10,8 +10,8 @@ // import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls import QtQuick.Controls 2.2 Item { diff --git a/interface/resources/qml/hifi/SpectatorCamera.qml b/interface/resources/qml/hifi/SpectatorCamera.qml index 09b722b906..4bf80e410b 100644 --- a/interface/resources/qml/hifi/SpectatorCamera.qml +++ b/interface/resources/qml/hifi/SpectatorCamera.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../styles-uit" +import "../controls-uit" as HifiControlsUit import "../controls" as HifiControls // references HMD, XXX from root context diff --git a/interface/resources/qml/hifi/TabletTextButton.qml b/interface/resources/qml/hifi/TabletTextButton.qml index 6c9e0331df..e5ff1d381d 100644 --- a/interface/resources/qml/hifi/TabletTextButton.qml +++ b/interface/resources/qml/hifi/TabletTextButton.qml @@ -10,7 +10,7 @@ import Hifi 1.0 import QtQuick 2.4 -import stylesUit 1.0 +import "../styles-uit" Rectangle { property alias text: label.text diff --git a/interface/resources/qml/hifi/TextButton.qml b/interface/resources/qml/hifi/TextButton.qml index 61588a9603..02e49d86e4 100644 --- a/interface/resources/qml/hifi/TextButton.qml +++ b/interface/resources/qml/hifi/TextButton.qml @@ -9,7 +9,7 @@ // import Hifi 1.0 import QtQuick 2.4 -import stylesUit 1.0 +import "../styles-uit" Rectangle { property alias text: label.text; diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index c05de26471..ab93752d92 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -18,8 +18,8 @@ import QtGraphicalEffects 1.0 import QtWebEngine 1.5 import QtWebChannel 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../styles-uit" +import "../controls-uit" as HifiControls import "../windows" import "../controls" diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..f4a708567a 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -15,8 +15,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls import "../../windows" import "./" as AudioControls diff --git a/interface/resources/qml/hifi/audio/AudioTabButton.qml b/interface/resources/qml/hifi/audio/AudioTabButton.qml index 32331ccb6e..3a3ed90f5e 100644 --- a/interface/resources/qml/hifi/audio/AudioTabButton.qml +++ b/interface/resources/qml/hifi/audio/AudioTabButton.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import controlsUit 1.0 as HifiControls -import stylesUit 1.0 +import "../../controls-uit" as HifiControls +import "../../styles-uit" TabButton { id: control diff --git a/interface/resources/qml/hifi/audio/CheckBox.qml b/interface/resources/qml/hifi/audio/CheckBox.qml index 5ab62a5091..3a954d4004 100644 --- a/interface/resources/qml/hifi/audio/CheckBox.qml +++ b/interface/resources/qml/hifi/audio/CheckBox.qml @@ -11,7 +11,7 @@ import QtQuick 2.7 -import controlsUit 1.0 as HifiControls +import "../../controls-uit" as HifiControls HifiControls.CheckBoxQQC2 { color: "white" diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index cfe55af9c4..2b9599a3cc 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -13,8 +13,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls RowLayout { property var sound: null; diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index 0740914440..5fff14e4a1 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -1,8 +1,9 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls Rectangle { id: root; diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml index d3c9cd1d5f..9d9db010fb 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml @@ -1,6 +1,6 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 +import "../../styles-uit" ShadowRectangle { id: header diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml index 36cb4b1080..f66c7121cb 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 import QtQuick.Window 2.2 -import stylesUit 1.0 +import "../../styles-uit" QtObject { readonly property QtObject colors: QtObject { diff --git a/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml index 8b28d4c66b..cb73e9fe71 100644 --- a/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml +++ b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml @@ -1,6 +1,6 @@ import QtQuick 2.9 -import controlsUit 1.0 -import stylesUit 1.0 +import "../../controls-uit" +import "../../styles-uit" ShadowRectangle { property int wearablesCount: 0 diff --git a/interface/resources/qml/hifi/avatarapp/BlueButton.qml b/interface/resources/qml/hifi/avatarapp/BlueButton.qml index 0cc84d5ba0..e668951517 100644 --- a/interface/resources/qml/hifi/avatarapp/BlueButton.qml +++ b/interface/resources/qml/hifi/avatarapp/BlueButton.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit HifiControlsUit.Button { HifiConstants { diff --git a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml index 780981a5a3..1387c0791a 100644 --- a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml +++ b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml @@ -1,7 +1,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/avatarapp/InputField.qml b/interface/resources/qml/hifi/avatarapp/InputField.qml index 2020d56c96..905518ef0f 100644 --- a/interface/resources/qml/hifi/avatarapp/InputField.qml +++ b/interface/resources/qml/hifi/avatarapp/InputField.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit TextField { id: textField diff --git a/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml index 6c2101498c..4b868b47ce 100644 --- a/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml +++ b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml @@ -1,5 +1,5 @@ -import controlsUit 1.0 as HifiControlsUit -import stylesUit 1.0 +import "../../controls-uit" as HifiControlsUit +import "../../styles-uit" import QtQuick 2.0 import QtQuick.Controls 2.2 diff --git a/interface/resources/qml/hifi/avatarapp/MessageBox.qml b/interface/resources/qml/hifi/avatarapp/MessageBox.qml index eb28745b1a..f111303214 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBox.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBox.qml @@ -1,7 +1,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index 38acce2b2c..71bfbb084d 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -2,8 +2,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml index a2c84fad47..c2d84bb371 100644 --- a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml +++ b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml @@ -1,4 +1,4 @@ -import stylesUit 1.0 +import "../../styles-uit" import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml index 51e1043702..3995446e49 100644 --- a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml +++ b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml @@ -1,4 +1,4 @@ -import stylesUit 1.0 +import "../../styles-uit" import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml index 3968fcb1ff..741fce3d8d 100644 --- a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml +++ b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml @@ -1,4 +1,4 @@ -import stylesUit 1.0 +import "../../styles-uit" import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml index 69aff47373..e2c456ec04 100644 --- a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml +++ b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml @@ -1,5 +1,5 @@ -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import QtQuick 2.9 import QtGraphicalEffects 1.0 diff --git a/interface/resources/qml/hifi/avatarapp/Vector3.qml b/interface/resources/qml/hifi/avatarapp/Vector3.qml index 698123104f..d77665f992 100644 --- a/interface/resources/qml/hifi/avatarapp/Vector3.qml +++ b/interface/resources/qml/hifi/avatarapp/Vector3.qml @@ -1,7 +1,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit import "../../controls" as HifiControls Row { diff --git a/interface/resources/qml/hifi/avatarapp/WhiteButton.qml b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml index d0a4a152db..dc729ae097 100644 --- a/interface/resources/qml/hifi/avatarapp/WhiteButton.qml +++ b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit HifiControlsUit.Button { HifiConstants { diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index ac6aa3d56c..b13f23f17d 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Controls 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet import "../common" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml index 8cfea0bcd9..9d9216c461 100644 --- a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml +++ b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 429f993817..1b77dcd3e9 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 import QtGraphicalEffects 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml index 6002747596..5f874d3f04 100644 --- a/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml +++ b/interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml index 1eb8af31e6..41eacd68d5 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml @@ -16,8 +16,8 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../../styles-uit" +import "../../../../controls-uit" as HifiControlsUit import "../../../../controls" as HifiControls import "../../wallet" as HifiWallet diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml index 9e1a967d50..9293dc83ab 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/RecipientDisplay.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.6 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../../styles-uit" +import "../../../../controls-uit" as HifiControlsUit import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index f204d943fe..0a5c3e8053 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.6 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../../styles-uit" +import "../../../../controls-uit" as HifiControlsUit import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 7721dc3142..d24344b40a 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index ee9858103c..c3d87ca2f5 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -17,8 +17,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.0 import QtQuick.Layouts 1.1 import Hifi 1.0 as Hifi -import stylesUit 1.0 as HifiStylesUit -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" as HifiStylesUit +import "../../../controls-uit" as HifiControlsUit diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 9d2df1a865..eeb9ac3c54 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet import TabletScriptingInterface 1.0 diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 8b7ebcf768..015ec3a172 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. import "../wallet" as HifiWallet diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 24ca5407b2..6d8fc3c33f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 import QtQuick.Controls 2.2 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml index b1fbb91c80..eadf1ca8a2 100644 --- a/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml +++ b/interface/resources/qml/hifi/commerce/wallet/NeedsLogIn.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml index 6ddfe0da1c..8451c90836 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseChange.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index 86d50e87ec..c4abd40d2a 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index 179ffcf707..e052b78876 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index f0b1ecd4e0..14ac696ef7 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml index da0d0d59d5..01df18352b 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml index 82933eebcb..599c6a1851 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml @@ -13,8 +13,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls // references XXX from root context diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 0cca581c4f..cbb77883df 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon import "../common/sendAsset" diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml b/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml index e7163a3641..19065ee542 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletChoice.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import "../common" as HifiCommerceCommon -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit Item { diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 1e78027f91..627da1d43f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -15,8 +15,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 import QtQuick.Controls 2.2 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index b793075843..dc6ce45a74 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -14,8 +14,8 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControlsUit +import "../../../styles-uit" +import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon diff --git a/interface/resources/qml/hifi/dialogs/AboutDialog.qml b/interface/resources/qml/hifi/dialogs/AboutDialog.qml index 3d5d1a94a3..b8e6e89aec 100644 --- a/interface/resources/qml/hifi/dialogs/AboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AboutDialog.qml @@ -10,7 +10,7 @@ import QtQuick 2.8 -import stylesUit 1.0 +import "../../styles-uit" import "../../windows" ScrollingWindow { diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index be17e65ab3..9a180a66f6 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls import "../../windows" import "../" diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index d26bf81e57..579aa1cb1e 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import stylesUit 1.0 +import "../../styles-uit" Rectangle { width: 480 diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index f665032b01..0eeb252049 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -14,8 +14,8 @@ import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls import "../../windows" import ".." diff --git a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml index 763f56b92b..afe06897df 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls import "../../windows" Rectangle { diff --git a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml index 213dca8b48..50df4dedbc 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import Hifi 1.0 as Hifi -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls Rectangle { id: root diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml index 4cfc99e0eb..24798af21a 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls import "../../windows" Rectangle { diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml index e86dfd7554..d5c5a5ee02 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls Column { id: root diff --git a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml index bb3d668850..ab53f03477 100644 --- a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml +++ b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls import "../../windows" Rectangle { diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index 6cd220307d..018c8f5737 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls import "../../windows" import "../" diff --git a/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml b/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml index b1aa8e5c45..ce1abc6154 100644 --- a/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml +++ b/interface/resources/qml/hifi/dialogs/content/ModelBrowserContent.qml @@ -1,7 +1,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 -import controlsUit 1.0 as HifiControls +import "../../../controls-uit" as HifiControls Column { width: pane.contentWidth diff --git a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml index 6b2aa331e8..e3115a5738 100644 --- a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml +++ b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml @@ -10,9 +10,9 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import stylesUit 1.0 +import "../../styles-uit" import "../../controls" -import controlsUit 1.0 as HifiControls +import "../../controls-uit" as HifiControls Rectangle { diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index b8bbd71f33..6706830537 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -11,9 +11,9 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import Qt.labs.settings 1.0 -import stylesUit 1.0 +import "../../styles-uit" import "../../controls" -import controlsUit 1.0 as HifiControls +import "../../controls-uit" as HifiControls import "../../dialogs" import "../../dialogs/preferences" import "tabletWindows" diff --git a/interface/resources/qml/hifi/tablet/EditEntityList.qml b/interface/resources/qml/hifi/tablet/EditEntityList.qml index d2fb99ea0a..d484885103 100644 --- a/interface/resources/qml/hifi/tablet/EditEntityList.qml +++ b/interface/resources/qml/hifi/tablet/EditEntityList.qml @@ -4,8 +4,8 @@ import QtWebChannel 1.0 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 -import controlsUit 1.0 as HifiControls -import stylesUit 1.0 +import "../../controls-uit" as HifiControls +import "../../styles-uit" WebView { diff --git a/interface/resources/qml/hifi/tablet/EditTabButton.qml b/interface/resources/qml/hifi/tablet/EditTabButton.qml index 5fc4341eb8..13894f4d15 100644 --- a/interface/resources/qml/hifi/tablet/EditTabButton.qml +++ b/interface/resources/qml/hifi/tablet/EditTabButton.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import controlsUit 1.0 as HifiControls -import stylesUit 1.0 +import "../../controls-uit" as HifiControls +import "../../styles-uit" TabButton { id: control diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index 332fab6112..4ac8755570 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -4,8 +4,8 @@ import QtWebChannel 1.0 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 -import controlsUit 1.0 as HifiControls -import stylesUit 1.0 +import "../../controls-uit" as HifiControls +import "../../styles-uit" TabBar { id: editTabView diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml index 76078b4afd..00084b8ca9 100644 --- a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml @@ -4,8 +4,8 @@ import QtWebChannel 1.0 import "../../controls" import "../toolbars" import QtGraphicalEffects 1.0 -import controlsUit 1.0 as HifiControls -import stylesUit 1.0 +import "../../controls-uit" as HifiControls +import "../../styles-uit" TabBar { id: editTabView diff --git a/interface/resources/qml/hifi/tablet/InputRecorder.qml b/interface/resources/qml/hifi/tablet/InputRecorder.qml index 9b63a612a8..527a6cacb4 100644 --- a/interface/resources/qml/hifi/tablet/InputRecorder.qml +++ b/interface/resources/qml/hifi/tablet/InputRecorder.qml @@ -9,8 +9,8 @@ import QtQuick 2.5 import Hifi 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../styles-uit" +import "../../controls-uit" as HifiControls import "../../windows" import "../../dialogs" diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml index dde372648b..526a42f8e2 100644 --- a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml @@ -13,8 +13,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Dialogs 1.2 as OriginalDialogs -import stylesUit 1.0 -import controlsUit 1.0 +import "../../styles-uit" +import "../../controls-uit" import "../dialogs" Rectangle { diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 9540979479..553a4fd59f 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import QtQuick.Dialogs 1.2 as OriginalDialogs -import stylesUit 1.0 -import controlsUit 1.0 +import "../../styles-uit" +import "../../controls-uit" import "../dialogs" Rectangle { diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index 684d12c9b4..c2aff08e35 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -9,9 +9,9 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import stylesUit 1.0 +import "../../styles-uit" import "../../controls" -import controlsUit 1.0 as HifiControls +import "../../controls-uit" as HifiControls import "." diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 0f26ba20aa..3d518289fb 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -18,8 +18,8 @@ import "../../styles" import "../../windows" import "../" import "../toolbars" -import stylesUit 1.0 as HifiStyles -import controlsUit 1.0 as HifiControls +import "../../styles-uit" as HifiStyles +import "../../controls-uit" as HifiControls import QtQuick.Controls 2.2 as QQC2 import QtQuick.Templates 2.2 as T diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index 934ed91995..1922b02f93 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -6,7 +6,7 @@ import QtQuick.Layouts 1.3 import TabletScriptingInterface 1.0 import "." -import stylesUit 1.0 +import "../../styles-uit" import "../audio" as HifiAudio Item { diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 267fb9f0cf..6540d53fca 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -7,7 +7,7 @@ import QtWebEngine 1.1 import "." -import stylesUit 1.0 +import "../../styles-uit" import "../../controls" FocusScope { diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml index 25db90c771..74f175e049 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import controlsUit 1.0 -import stylesUit 1.0 +import "../../controls-uit" +import "../../styles-uit" Item { id: root diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index 73b0405984..b632a17e57 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import TabletScriptingInterface 1.0 -import stylesUit 1.0 +import "../../styles-uit" import "." FocusScope { diff --git a/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml b/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml index ce4e641476..d69d760b95 100644 --- a/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletModelBrowserDialog.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 -import controlsUit 1.0 as HifiControls -import stylesUit 1.0 +import "../../controls-uit" as HifiControls +import "../../styles-uit" import "../dialogs/content" Item { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index 8e91655dda..871d1c92a9 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -16,8 +16,8 @@ import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.3 import ".." -import controlsUit 1.0 -import stylesUit 1.0 +import "../../../controls-uit" +import "../../../styles-uit" import "../../../windows" import "../../../dialogs/fileDialog" diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index 57ca705352..3708f75114 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import "." import "./preferences" -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "../../../styles-uit" +import "../../../controls-uit" as HifiControls Item { id: dialog diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 57fdeb482b..6ac3f706e4 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -12,8 +12,8 @@ import QtQuick 2.5 import Hifi 1.0 import "../../../../dialogs/preferences" -import controlsUit 1.0 as HiFiControls -import stylesUit 1.0 +import "../../../../controls-uit" as HiFiControls +import "../../../../styles-uit" import "." Preference { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml index 36b927f5f9..8c0e934971 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletBrowsablePreference.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "../../../../dialogs" -import controlsUit 1.0 +import "../../../../controls-uit" import "../" Preference { diff --git a/interface/resources/qml/stylesUit/+android/HifiConstants.qml b/interface/resources/qml/styles-uit/+android/HifiConstants.qml similarity index 100% rename from interface/resources/qml/stylesUit/+android/HifiConstants.qml rename to interface/resources/qml/styles-uit/+android/HifiConstants.qml diff --git a/interface/resources/qml/stylesUit/AnonymousProRegular.qml b/interface/resources/qml/styles-uit/AnonymousProRegular.qml similarity index 100% rename from interface/resources/qml/stylesUit/AnonymousProRegular.qml rename to interface/resources/qml/styles-uit/AnonymousProRegular.qml diff --git a/interface/resources/qml/stylesUit/ButtonLabel.qml b/interface/resources/qml/styles-uit/ButtonLabel.qml similarity index 100% rename from interface/resources/qml/stylesUit/ButtonLabel.qml rename to interface/resources/qml/styles-uit/ButtonLabel.qml diff --git a/interface/resources/qml/stylesUit/FiraSansRegular.qml b/interface/resources/qml/styles-uit/FiraSansRegular.qml similarity index 100% rename from interface/resources/qml/stylesUit/FiraSansRegular.qml rename to interface/resources/qml/styles-uit/FiraSansRegular.qml diff --git a/interface/resources/qml/stylesUit/FiraSansSemiBold.qml b/interface/resources/qml/styles-uit/FiraSansSemiBold.qml similarity index 100% rename from interface/resources/qml/stylesUit/FiraSansSemiBold.qml rename to interface/resources/qml/styles-uit/FiraSansSemiBold.qml diff --git a/interface/resources/qml/stylesUit/HiFiGlyphs.qml b/interface/resources/qml/styles-uit/HiFiGlyphs.qml similarity index 100% rename from interface/resources/qml/stylesUit/HiFiGlyphs.qml rename to interface/resources/qml/styles-uit/HiFiGlyphs.qml diff --git a/interface/resources/qml/stylesUit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml similarity index 100% rename from interface/resources/qml/stylesUit/HifiConstants.qml rename to interface/resources/qml/styles-uit/HifiConstants.qml diff --git a/interface/resources/qml/stylesUit/IconButton.qml b/interface/resources/qml/styles-uit/IconButton.qml similarity index 100% rename from interface/resources/qml/stylesUit/IconButton.qml rename to interface/resources/qml/styles-uit/IconButton.qml diff --git a/interface/resources/qml/stylesUit/InfoItem.qml b/interface/resources/qml/styles-uit/InfoItem.qml similarity index 100% rename from interface/resources/qml/stylesUit/InfoItem.qml rename to interface/resources/qml/styles-uit/InfoItem.qml diff --git a/interface/resources/qml/stylesUit/InputLabel.qml b/interface/resources/qml/styles-uit/InputLabel.qml similarity index 100% rename from interface/resources/qml/stylesUit/InputLabel.qml rename to interface/resources/qml/styles-uit/InputLabel.qml diff --git a/interface/resources/qml/stylesUit/ListItem.qml b/interface/resources/qml/styles-uit/ListItem.qml similarity index 100% rename from interface/resources/qml/stylesUit/ListItem.qml rename to interface/resources/qml/styles-uit/ListItem.qml diff --git a/interface/resources/qml/stylesUit/Logs.qml b/interface/resources/qml/styles-uit/Logs.qml similarity index 100% rename from interface/resources/qml/stylesUit/Logs.qml rename to interface/resources/qml/styles-uit/Logs.qml diff --git a/interface/resources/qml/stylesUit/OverlayTitle.qml b/interface/resources/qml/styles-uit/OverlayTitle.qml similarity index 100% rename from interface/resources/qml/stylesUit/OverlayTitle.qml rename to interface/resources/qml/styles-uit/OverlayTitle.qml diff --git a/interface/resources/qml/stylesUit/RalewayBold.qml b/interface/resources/qml/styles-uit/RalewayBold.qml similarity index 100% rename from interface/resources/qml/stylesUit/RalewayBold.qml rename to interface/resources/qml/styles-uit/RalewayBold.qml diff --git a/interface/resources/qml/stylesUit/RalewayLight.qml b/interface/resources/qml/styles-uit/RalewayLight.qml similarity index 100% rename from interface/resources/qml/stylesUit/RalewayLight.qml rename to interface/resources/qml/styles-uit/RalewayLight.qml diff --git a/interface/resources/qml/stylesUit/RalewayRegular.qml b/interface/resources/qml/styles-uit/RalewayRegular.qml similarity index 100% rename from interface/resources/qml/stylesUit/RalewayRegular.qml rename to interface/resources/qml/styles-uit/RalewayRegular.qml diff --git a/interface/resources/qml/stylesUit/RalewaySemiBold.qml b/interface/resources/qml/styles-uit/RalewaySemiBold.qml similarity index 100% rename from interface/resources/qml/stylesUit/RalewaySemiBold.qml rename to interface/resources/qml/styles-uit/RalewaySemiBold.qml diff --git a/interface/resources/qml/stylesUit/SectionName.qml b/interface/resources/qml/styles-uit/SectionName.qml similarity index 100% rename from interface/resources/qml/stylesUit/SectionName.qml rename to interface/resources/qml/styles-uit/SectionName.qml diff --git a/interface/resources/qml/stylesUit/Separator.qml b/interface/resources/qml/styles-uit/Separator.qml similarity index 97% rename from interface/resources/qml/stylesUit/Separator.qml rename to interface/resources/qml/styles-uit/Separator.qml index d9f11e192c..4134b928a7 100644 --- a/interface/resources/qml/stylesUit/Separator.qml +++ b/interface/resources/qml/styles-uit/Separator.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import "." +import "../styles-uit" Item { // Size diff --git a/interface/resources/qml/stylesUit/ShortcutText.qml b/interface/resources/qml/styles-uit/ShortcutText.qml similarity index 100% rename from interface/resources/qml/stylesUit/ShortcutText.qml rename to interface/resources/qml/styles-uit/ShortcutText.qml diff --git a/interface/resources/qml/stylesUit/TabName.qml b/interface/resources/qml/styles-uit/TabName.qml similarity index 100% rename from interface/resources/qml/stylesUit/TabName.qml rename to interface/resources/qml/styles-uit/TabName.qml diff --git a/interface/resources/qml/stylesUit/TextFieldInput.qml b/interface/resources/qml/styles-uit/TextFieldInput.qml similarity index 100% rename from interface/resources/qml/stylesUit/TextFieldInput.qml rename to interface/resources/qml/styles-uit/TextFieldInput.qml diff --git a/interface/resources/qml/stylesUit/qmldir b/interface/resources/qml/styles-uit/qmldir similarity index 100% rename from interface/resources/qml/stylesUit/qmldir rename to interface/resources/qml/styles-uit/qmldir diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml index efaea6be8a..f8fd9f4e6c 100644 --- a/interface/resources/qml/windows/Decoration.qml +++ b/interface/resources/qml/windows/Decoration.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import stylesUit 1.0 +import "../styles-uit" Rectangle { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/DefaultFrame.qml b/interface/resources/qml/windows/DefaultFrame.qml index 5a366e367b..60e744bec3 100644 --- a/interface/resources/qml/windows/DefaultFrame.qml +++ b/interface/resources/qml/windows/DefaultFrame.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import "." -import stylesUit 1.0 +import "../styles-uit" Frame { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml index fb0dd55985..1ddd83976e 100644 --- a/interface/resources/qml/windows/DefaultFrameDecoration.qml +++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import stylesUit 1.0 +import "../styles-uit" Decoration { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/Fadable.qml b/interface/resources/qml/windows/Fadable.qml index 6d88fb067a..406c6be556 100644 --- a/interface/resources/qml/windows/Fadable.qml +++ b/interface/resources/qml/windows/Fadable.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 -import stylesUit 1.0 +import "../styles-uit" // Enable window visibility transitions FocusScope { diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 7b0fbf8d8c..271d4f2e07 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 -import stylesUit 1.0 +import "../styles-uit" import "../js/Utils.js" as Utils Item { diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index ae149224e3..cb23ccd5ad 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import "." -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" Frame { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml index 4cab96701e..c156b80388 100644 --- a/interface/resources/qml/windows/ScrollingWindow.qml +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -14,8 +14,8 @@ import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import "." -import stylesUit 1.0 -import controlsUit 1.0 as HiFiControls +import "../styles-uit" +import "../controls-uit" as HiFiControls // FIXME how do I set the initial position of a window without // overriding places where the a individual client of the window diff --git a/interface/resources/qml/windows/TabletModalFrame.qml b/interface/resources/qml/windows/TabletModalFrame.qml index 1e9310eb5a..550eec8357 100644 --- a/interface/resources/qml/windows/TabletModalFrame.qml +++ b/interface/resources/qml/windows/TabletModalFrame.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import "." -import controlsUit 1.0 -import stylesUit 1.0 +import "../controls-uit" +import "../styles-uit" Rectangle { diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml index bb2bada498..20c86afb5e 100644 --- a/interface/resources/qml/windows/ToolFrame.qml +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import stylesUit 1.0 +import "../styles-uit" Frame { HifiConstants { id: hifi } diff --git a/interface/resources/qml/windows/ToolFrameDecoration.qml b/interface/resources/qml/windows/ToolFrameDecoration.qml index 4f149037b3..ba36a2a38c 100644 --- a/interface/resources/qml/windows/ToolFrameDecoration.qml +++ b/interface/resources/qml/windows/ToolFrameDecoration.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import stylesUit 1.0 +import "../styles-uit" Decoration { id: root diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 9f180af55d..835967c628 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "." -import stylesUit 1.0 +import "../styles-uit" // FIXME how do I set the initial position of a window without // overriding places where the a individual client of the window diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index f67a356078..74098f69c7 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -250,7 +250,6 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { engine->setNetworkAccessManagerFactory(new QmlNetworkAccessManagerFactory); auto importList = engine->importPathList(); - importList.insert(importList.begin(), PathUtils::resourcesPath() + "qml/"); importList.insert(importList.begin(), PathUtils::resourcesPath()); engine->setImportPathList(importList); for (const auto& path : importList) { diff --git a/scripts/developer/tests/ControlsGallery.qml b/scripts/developer/tests/ControlsGallery.qml index 9685fa6fe8..ceb8a26dc9 100644 --- a/scripts/developer/tests/ControlsGallery.qml +++ b/scripts/developer/tests/ControlsGallery.qml @@ -2,9 +2,16 @@ import QtQuick 2.10 import QtQuick.Window 2.10 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit -import stylesUit 1.0 as HifiStylesUit -import controlsUit 1.0 as HifiControlsUit +//uncomment to use from qmlscratch tool +//import '../../../interface/resources/qml/controls-uit' as HifiControlsUit +//import '../../../interface/resources/qml/styles-uit' + +//uncomment to use with HIFI_USE_SOURCE_TREE_RESOURCES=1 +//import '../../../resources/qml/controls-uit' as HifiControlsUit +//import '../../../resources/qml/styles-uit' Item { visible: true diff --git a/scripts/developer/utilities/audio/Stats.qml b/scripts/developer/utilities/audio/Stats.qml index e2291e485d..f359e9b04c 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 controlsUit 1.0 as HifiControls +import "qrc:////qml//controls-uit" as HifiControls Column { id: stats diff --git a/scripts/developer/utilities/audio/TabletStats.qml b/scripts/developer/utilities/audio/TabletStats.qml index b50acabec4..2f8d212a2a 100644 --- a/scripts/developer/utilities/audio/TabletStats.qml +++ b/scripts/developer/utilities/audio/TabletStats.qml @@ -11,7 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import stylesUit 1.0 + +import "qrc:////qml//styles-uit" Item { id: dialog diff --git a/scripts/developer/utilities/lib/jet/qml/TaskList.qml b/scripts/developer/utilities/lib/jet/qml/TaskList.qml index 166f604666..5b1aa0afb5 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskList.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskList.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "../jet.js" as Jet diff --git a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml index 0f083aa72c..2c75865698 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "../jet.js" as Jet diff --git a/scripts/developer/utilities/lib/plotperf/Color.qml b/scripts/developer/utilities/lib/plotperf/Color.qml index 1ad72fe2e6..15d7f9fcc9 100644 --- a/scripts/developer/utilities/lib/plotperf/Color.qml +++ b/scripts/developer/utilities/lib/plotperf/Color.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls Item { diff --git a/scripts/developer/utilities/render/antialiasing.qml b/scripts/developer/utilities/render/antialiasing.qml index 5abfd30935..1a8f9dac2d 100644 --- a/scripts/developer/utilities/render/antialiasing.qml +++ b/scripts/developer/utilities/render/antialiasing.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "configSlider" import "../lib/plotperf" diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index bf9089d82c..41de77fb09 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls Item { diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml index ff16cb32ad..01b14f3d48 100644 --- a/scripts/developer/utilities/render/configSlider/RichSlider.qml +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 as Original import QtQuick.Controls.Styles 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls Item { diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index 64e00acdac..a9479b2935 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "configSlider" import "../lib/jet/qml" as Jet diff --git a/scripts/developer/utilities/render/engineInspector.qml b/scripts/developer/utilities/render/engineInspector.qml index 16dd8eb985..1b9941e64e 100644 --- a/scripts/developer/utilities/render/engineInspector.qml +++ b/scripts/developer/utilities/render/engineInspector.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "../lib/jet/qml" as Jet diff --git a/scripts/developer/utilities/render/highlight.qml b/scripts/developer/utilities/render/highlight.qml index d8af2a828e..88d6a807ae 100644 --- a/scripts/developer/utilities/render/highlight.qml +++ b/scripts/developer/utilities/render/highlight.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "configSlider" import "../lib/plotperf" import "highlight" diff --git a/scripts/developer/utilities/render/highlight/HighlightStyle.qml b/scripts/developer/utilities/render/highlight/HighlightStyle.qml index 475aadfdce..371b7e81f7 100644 --- a/scripts/developer/utilities/render/highlight/HighlightStyle.qml +++ b/scripts/developer/utilities/render/highlight/HighlightStyle.qml @@ -12,8 +12,8 @@ import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 import "../configSlider" import "../../lib/plotperf" -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls Item { id: root diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 892b43d8be..889d8db836 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "../lib/plotperf" import "configSlider" diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index a1d6777a68..464fe00eb9 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -12,8 +12,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "configSlider" diff --git a/scripts/developer/utilities/render/transition.qml b/scripts/developer/utilities/render/transition.qml index c150c523f9..f74468a273 100644 --- a/scripts/developer/utilities/render/transition.qml +++ b/scripts/developer/utilities/render/transition.qml @@ -13,8 +13,8 @@ import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 import QtQuick.Dialogs 1.0 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "configSlider" import "../lib/plotperf" diff --git a/scripts/developer/utilities/workload/workloadInspector.qml b/scripts/developer/utilities/workload/workloadInspector.qml index 746a572f29..2eaa9d8133 100644 --- a/scripts/developer/utilities/workload/workloadInspector.qml +++ b/scripts/developer/utilities/workload/workloadInspector.qml @@ -11,8 +11,8 @@ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 -import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls import "../render/configSlider" import "../lib/jet/qml" as Jet import "../lib/plotperf" diff --git a/tests-manual/ui/qml/ControlsGalleryWindow.qml b/tests-manual/ui/qml/ControlsGalleryWindow.qml deleted file mode 100644 index 32fd62da36..0000000000 --- a/tests-manual/ui/qml/ControlsGalleryWindow.qml +++ /dev/null @@ -1,14 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Window 2.3 -import QtQuick.Controls 1.4 -import '../../../scripts/developer/tests' as Tests - -ApplicationWindow { - width: 640 - height: 480 - visible: true - - Tests.ControlsGallery { - anchors.fill: parent - } -} diff --git a/tests-manual/ui/qml/Palettes.qml b/tests-manual/ui/qml/Palettes.qml new file mode 100644 index 0000000000..2bdf6eba8b --- /dev/null +++ b/tests-manual/ui/qml/Palettes.qml @@ -0,0 +1,150 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +Rectangle { + color: "teal" + height: 512 + width: 192 + SystemPalette { id: sp; colorGroup: SystemPalette.Active } + SystemPalette { id: spi; colorGroup: SystemPalette.Inactive } + SystemPalette { id: spd; colorGroup: SystemPalette.Disabled } + + Column { + anchors.margins: 8 + anchors.fill: parent + spacing: 8 + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "base" } + Rectangle { height: parent.height; width: 16; color: sp.base } + Rectangle { height: parent.height; width: 16; color: spi.base } + Rectangle { height: parent.height; width: 16; color: spd.base } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "alternateBase" } + Rectangle { height: parent.height; width: 16; color: sp.alternateBase } + Rectangle { height: parent.height; width: 16; color: spi.alternateBase } + Rectangle { height: parent.height; width: 16; color: spd.alternateBase } + } + Item { + height: 16 + width:parent.width + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "dark" } + Rectangle { height: parent.height; width: 16; color: sp.dark } + Rectangle { height: parent.height; width: 16; color: spi.dark } + Rectangle { height: parent.height; width: 16; color: spd.dark } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "mid" } + Rectangle { height: parent.height; width: 16; color: sp.mid } + Rectangle { height: parent.height; width: 16; color: spi.mid } + Rectangle { height: parent.height; width: 16; color: spd.mid } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "mid light" } + Rectangle { height: parent.height; width: 16; color: sp.midlight } + Rectangle { height: parent.height; width: 16; color: spi.midlight } + Rectangle { height: parent.height; width: 16; color: spd.midlight } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "light" } + Rectangle { height: parent.height; width: 16; color: sp.light} + Rectangle { height: parent.height; width: 16; color: spi.light} + Rectangle { height: parent.height; width: 16; color: spd.light} + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "shadow" } + Rectangle { height: parent.height; width: 16; color: sp.shadow} + Rectangle { height: parent.height; width: 16; color: spi.shadow} + Rectangle { height: parent.height; width: 16; color: spd.shadow} + } + Item { + height: 16 + width:parent.width + } + + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "text" } + Rectangle { height: parent.height; width: 16; color: sp.text } + Rectangle { height: parent.height; width: 16; color: spi.text } + Rectangle { height: parent.height; width: 16; color: spd.text } + } + Item { + height: 16 + width:parent.width + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "window" } + Rectangle { height: parent.height; width: 16; color: sp.window } + Rectangle { height: parent.height; width: 16; color: spi.window } + Rectangle { height: parent.height; width: 16; color: spd.window } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "window text" } + Rectangle { height: parent.height; width: 16; color: sp.windowText } + Rectangle { height: parent.height; width: 16; color: spi.windowText } + Rectangle { height: parent.height; width: 16; color: spd.windowText } + } + Item { + height: 16 + width:parent.width + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "button" } + Rectangle { height: parent.height; width: 16; color: sp.button } + Rectangle { height: parent.height; width: 16; color: spi.button } + Rectangle { height: parent.height; width: 16; color: spd.button } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "buttonText" } + Rectangle { height: parent.height; width: 16; color: sp.buttonText } + Rectangle { height: parent.height; width: 16; color: spi.buttonText } + Rectangle { height: parent.height; width: 16; color: spd.buttonText } + } + Item { + height: 16 + width:parent.width + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "highlight" } + Rectangle { height: parent.height; width: 16; color: sp.highlight } + Rectangle { height: parent.height; width: 16; color: spi.highlight } + Rectangle { height: parent.height; width: 16; color: spd.highlight } + } + Row { + width: parent.width + height: 16 + Text { height: parent.height; width: 128; text: "highlighted text" } + Rectangle { height: parent.height; width: 16; color: sp.highlightedText} + Rectangle { height: parent.height; width: 16; color: spi.highlightedText} + Rectangle { height: parent.height; width: 16; color: spd.highlightedText} + } + } +} diff --git a/tests-manual/ui/qml/ScrollingGraph.qml b/tests-manual/ui/qml/ScrollingGraph.qml new file mode 100644 index 0000000000..55523a23f4 --- /dev/null +++ b/tests-manual/ui/qml/ScrollingGraph.qml @@ -0,0 +1,111 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +Rectangle { + id: root + property int size: 64 + width: size + height: size + color: 'black' + property int controlId: 0 + property real value: 0.5 + property int scrollWidth: 1 + property real min: 0.0 + property real max: 1.0 + property bool log: false + property real range: max - min + property color lineColor: 'yellow' + property bool bar: false + property real lastHeight: -1 + property string label: "" + + function update() { + value = Controller.getValue(controlId); + if (log) { + var log = Math.log(10) / Math.log(Math.abs(value)); + var sign = Math.sign(value); + value = log * sign; + } + canvas.requestPaint(); + } + + function drawHeight() { + if (value < min) { + return 0; + } + if (value > max) { + return height; + } + return ((value - min) / range) * height; + } + + Timer { + interval: 50; running: true; repeat: true + onTriggered: root.update() + } + + Canvas { + id: canvas + anchors.fill: parent + antialiasing: false + + Text { + anchors.top: parent.top + text: root.label + color: 'white' + } + + Text { + anchors.right: parent.right + anchors.top: parent.top + text: root.max + color: 'white' + } + + Text { + anchors.right: parent.right + anchors.bottom: parent.bottom + text: root.min + color: 'white' + } + + function scroll() { + var ctx = canvas.getContext('2d'); + var image = ctx.getImageData(0, 0, canvas.width, canvas.height); + ctx.beginPath(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(image, -root.scrollWidth, 0, canvas.width, canvas.height) + ctx.restore() + } + + onPaint: { + scroll(); + var ctx = canvas.getContext('2d'); + ctx.save(); + var currentHeight = root.drawHeight(); + if (root.lastHeight == -1) { + root.lastHeight = currentHeight + } + +// var x = canvas.width - root.drawWidth; +// var y = canvas.height - drawHeight; +// ctx.fillStyle = root.color +// ctx.fillRect(x, y, root.drawWidth, root.bar ? drawHeight : 1) +// ctx.fill(); +// ctx.restore() + + + ctx.beginPath(); + ctx.lineWidth = 1 + ctx.strokeStyle = root.lineColor + ctx.moveTo(canvas.width - root.scrollWidth, root.lastHeight).lineTo(canvas.width, currentHeight) + ctx.stroke() + ctx.restore() + root.lastHeight = currentHeight + } + } +} + + diff --git a/tests-manual/ui/qml/StubMenu.qml b/tests-manual/ui/qml/StubMenu.qml new file mode 100644 index 0000000000..fd0298988a --- /dev/null +++ b/tests-manual/ui/qml/StubMenu.qml @@ -0,0 +1,730 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import "../../../interface/resources/qml/hifi" + +Menu { + property var menuOption: MenuOption {} + Item { + Action { + id: login; + text: menuOption.login + } + Action { + id: update; + text: "Update"; + enabled: false + } + Action { + id: crashReporter; + text: "Crash Reporter..."; + enabled: false + } + Action { + id: help; + text: menuOption.help + onTriggered: Application.showHelp() + } + Action { + id: aboutApp; + text: menuOption.aboutApp + } + Action { + id: quit; + text: menuOption.quit + } + + ExclusiveGroup { id: renderResolutionGroup } + Action { + id: renderResolutionOne; + exclusiveGroup: renderResolutionGroup; + text: menuOption.renderResolutionOne; + checkable: true; + checked: true + } + Action { + id: renderResolutionTwoThird; + exclusiveGroup: renderResolutionGroup; + text: menuOption.renderResolutionTwoThird; + checkable: true + } + Action { + id: renderResolutionHalf; + exclusiveGroup: renderResolutionGroup; + text: menuOption.renderResolutionHalf; + checkable: true + } + Action { + id: renderResolutionThird; + exclusiveGroup: renderResolutionGroup; + text: menuOption.renderResolutionThird; + checkable: true + } + Action { + id: renderResolutionQuarter; + exclusiveGroup: renderResolutionGroup; + text: menuOption.renderResolutionQuarter; + checkable: true + } + + ExclusiveGroup { id: ambientLightGroup } + Action { + id: renderAmbientLightGlobal; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLightGlobal; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight0; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight0; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight1; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight1; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight2; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight2; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight3; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight3; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight4; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight4; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight5; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight5; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight6; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight6; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight7; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight7; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight8; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight8; + checkable: true; + checked: true + } + Action { + id: renderAmbientLight9; + exclusiveGroup: ambientLightGroup; + text: menuOption.renderAmbientLight9; + checkable: true; + checked: true + } + Action { + id: preferences + shortcut: StandardKey.Preferences + text: menuOption.preferences + onTriggered: dialogsManager.editPreferences() + } + + } + + Menu { + title: "File" + MenuItem { + action: login + } + MenuItem { + action: update + } + MenuItem { + action: help + } + MenuItem { + action: crashReporter + } + MenuItem { + action: aboutApp + } + MenuItem { + action: quit + } + } + + Menu { + title: "Edit" + MenuItem { + text: "Undo" } + MenuItem { + text: "Redo" } + MenuItem { + text: menuOption.runningScripts + } + MenuItem { + text: menuOption.loadScript + } + MenuItem { + text: menuOption.loadScriptURL + } + MenuItem { + text: menuOption.stopAllScripts + } + MenuItem { + text: menuOption.reloadAllScripts + } + MenuItem { + text: menuOption.scriptEditor + } + MenuItem { + text: menuOption.console_ + } + MenuItem { + text: menuOption.reloadContent + } + MenuItem { + text: menuOption.packageModel + } + } + + Menu { + title: "Audio" + MenuItem { + text: menuOption.muteAudio; + checkable: true + } + MenuItem { + text: menuOption.audioTools; + checkable: true + } + } + Menu { + title: "Avatar" + // Avatar > Attachments... + MenuItem { + text: menuOption.attachments + } + Menu { + title: "Size" + // Avatar > Size > Increase + MenuItem { + text: menuOption.increaseAvatarSize + } + // Avatar > Size > Decrease + MenuItem { + text: menuOption.decreaseAvatarSize + } + // Avatar > Size > Reset + MenuItem { + text: menuOption.resetAvatarSize + } + } + // Avatar > Reset Sensors + MenuItem { + text: menuOption.resetSensors + } + } + Menu { + title: "Display" + } + Menu { + title: "View" + ExclusiveGroup { + id: cameraModeGroup + } + + MenuItem { + text: menuOption.firstPerson; + checkable: true; + exclusiveGroup: cameraModeGroup + } + MenuItem { + text: menuOption.thirdPerson; + checkable: true; + exclusiveGroup: cameraModeGroup + } + MenuItem { + text: menuOption.fullscreenMirror; + checkable: true; + exclusiveGroup: cameraModeGroup + } + MenuItem { + text: menuOption.independentMode; + checkable: true; + exclusiveGroup: cameraModeGroup + } + MenuItem { + text: menuOption.cameraEntityMode; + checkable: true; + exclusiveGroup: cameraModeGroup + } + MenuSeparator{} + MenuItem { + text: menuOption.miniMirror; + checkable: true + } + } + Menu { + title: "Navigate" + MenuItem { + text: "Home" } + MenuItem { + text: menuOption.addressBar + } + MenuItem { + text: "Directory" } + MenuItem { + text: menuOption.copyAddress + } + MenuItem { + text: menuOption.copyPath + } + } + Menu { + title: "Settings" + MenuItem { + text: "Advanced Menus" } + MenuItem { + text: "Developer Menus" } + MenuItem { + text: menuOption.preferences + } + MenuItem { + text: "Avatar..." } + MenuItem { + text: "Audio..." } + MenuItem { + text: "LOD..." } + MenuItem { + text: menuOption.inputMenu + } + } + Menu { + title: "Developer" + Menu { + title: "Render" + MenuItem { + text: menuOption.atmosphere; + checkable: true + } + MenuItem { + text: menuOption.worldAxes; + checkable: true + } + MenuItem { + text: menuOption.debugAmbientOcclusion; + checkable: true + } + MenuItem { + text: menuOption.antialiasing; + checkable: true + } + MenuItem { + text: menuOption.stars; + checkable: true + } + Menu { + title: menuOption.renderAmbientLight + MenuItem { + action: renderAmbientLightGlobal; } + MenuItem { + action: renderAmbientLight0; } + MenuItem { + action: renderAmbientLight1; } + MenuItem { + action: renderAmbientLight2; } + MenuItem { + action: renderAmbientLight3; } + MenuItem { + action: renderAmbientLight4; } + MenuItem { + action: renderAmbientLight5; } + MenuItem { + action: renderAmbientLight6; } + MenuItem { + action: renderAmbientLight7; } + MenuItem { + action: renderAmbientLight8; } + MenuItem { + action: renderAmbientLight9; } + } + MenuItem { + text: menuOption.throttleFPSIfNotFocus; + checkable: true + } + Menu { + title: menuOption.renderResolution + MenuItem { + action: renderResolutionOne + } + MenuItem { + action: renderResolutionTwoThird + } + MenuItem { + action: renderResolutionHalf + } + MenuItem { + action: renderResolutionThird + } + MenuItem { + action: renderResolutionQuarter + } + } + MenuItem { + text: menuOption.lodTools + } + } + Menu { + title: "Assets" + MenuItem { + text: menuOption.uploadAsset + } + MenuItem { + text: menuOption.assetMigration + } + } + Menu { + title: "Avatar" + Menu { + title: "Face Tracking" + MenuItem { + text: menuOption.noFaceTracking; + checkable: true + } + MenuItem { + text: menuOption.faceshift; + checkable: true + } + MenuItem { + text: menuOption.useCamera; + checkable: true + } + MenuSeparator{} + MenuItem { + text: menuOption.binaryEyelidControl; + checkable: true + } + MenuItem { + text: menuOption.coupleEyelids; + checkable: true + } + MenuItem { + text: menuOption.useAudioForMouth; + checkable: true + } + MenuItem { + text: menuOption.velocityFilter; + checkable: true + } + MenuItem { + text: menuOption.calibrateCamera + } + MenuSeparator{} + MenuItem { + text: menuOption.muteFaceTracking; + checkable: true + } + MenuItem { + text: menuOption.autoMuteAudio; + checkable: true + } + } + Menu { + title: "Eye Tracking" + MenuItem { + text: menuOption.sMIEyeTracking; + checkable: true + } + Menu { + title: "Calibrate" + MenuItem { + text: menuOption.onePointCalibration + } + MenuItem { + text: menuOption.threePointCalibration + } + MenuItem { + text: menuOption.fivePointCalibration + } + } + MenuItem { + text: menuOption.simulateEyeTracking; + checkable: true + } + } + MenuItem { + text: menuOption.avatarReceiveStats; + checkable: true + } + MenuItem { + text: menuOption.renderBoundingCollisionShapes; + checkable: true + } + MenuItem { + text: menuOption.renderLookAtVectors; + checkable: true + } + MenuItem { + text: menuOption.renderLookAtTargets; + checkable: true + } + MenuItem { + text: menuOption.renderFocusIndicator; + checkable: true + } + MenuItem { + text: menuOption.showWhosLookingAtMe; + checkable: true + } + MenuItem { + text: menuOption.fixGaze; + checkable: true + } + MenuItem { + text: menuOption.animDebugDrawDefaultPose; + checkable: true + } + MenuItem { + text: menuOption.animDebugDrawAnimPose; + checkable: true + } + MenuItem { + text: menuOption.animDebugDrawPosition; + checkable: true + } + MenuItem { + text: menuOption.meshVisible; + checkable: true + } + MenuItem { + text: menuOption.disableEyelidAdjustment; + checkable: true + } + MenuItem { + text: menuOption.turnWithHead; + checkable: true + } + MenuItem { + text: menuOption.keyboardMotorControl; + checkable: true + } + MenuItem { + text: menuOption.scriptedMotorControl; + checkable: true + } + MenuItem { + text: menuOption.enableCharacterController; + checkable: true + } + } + Menu { + title: "Hands" + MenuItem { + text: menuOption.displayHandTargets; + checkable: true + } + MenuItem { + text: menuOption.lowVelocityFilter; + checkable: true + } + Menu { + title: "Leap Motion" + MenuItem { + text: menuOption.leapMotionOnHMD; + checkable: true + } + } + } + Menu { + title: "Entities" + MenuItem { + text: menuOption.octreeStats + } + MenuItem { + text: menuOption.showRealtimeEntityStats; + checkable: true + } + } + Menu { + title: "Network" + MenuItem { + text: menuOption.reloadContent + } + MenuItem { + text: menuOption.disableNackPackets; + checkable: true + } + MenuItem { + text: menuOption.disableActivityLogger; + checkable: true + } + MenuItem { + text: menuOption.cachesSize + } + MenuItem { + text: menuOption.diskCacheEditor + } + MenuItem { + text: menuOption.showDSConnectTable + } + MenuItem { + text: menuOption.bandwidthDetails + } + } + Menu { + title: "Timing" + Menu { + title: "Performance Timer" + MenuItem { + text: menuOption.displayDebugTimingDetails; + checkable: true + } + MenuItem { + text: menuOption.onlyDisplayTopTen; + checkable: true + } + MenuItem { + text: menuOption.expandUpdateTiming; + checkable: true + } + MenuItem { + text: menuOption.expandMyAvatarTiming; + checkable: true + } + MenuItem { + text: menuOption.expandMyAvatarSimulateTiming; + checkable: true + } + MenuItem { + text: menuOption.expandOtherAvatarTiming; + checkable: true + } + MenuItem { + text: menuOption.expandPaintGLTiming; + checkable: true + } + } + MenuItem { + text: menuOption.frameTimer; + checkable: true + } + MenuItem { + text: menuOption.runTimingTests + } + MenuItem { + text: menuOption.pipelineWarnings; + checkable: true + } + MenuItem { + text: menuOption.logExtraTimings; + checkable: true + } + MenuItem { + text: menuOption.suppressShortTimings; + checkable: true + } + } + Menu { + title: "Audio" + MenuItem { + text: menuOption.audioNoiseReduction; + checkable: true + } + MenuItem { + text: menuOption.echoServerAudio; + checkable: true + } + MenuItem { + text: menuOption.echoLocalAudio; + checkable: true + } + MenuItem { + text: menuOption.muteEnvironment + } + Menu { + title: "Audio" + MenuItem { + text: menuOption.audioScope; + checkable: true + } + MenuItem { + text: menuOption.audioScopePause; + checkable: true + } + Menu { + title: "Display Frames" + ExclusiveGroup { + id: audioScopeFramesGroup + } + MenuItem { + exclusiveGroup: audioScopeFramesGroup; + text: menuOption.audioScopeFiveFrames; + checkable: true + } + MenuItem { + exclusiveGroup: audioScopeFramesGroup; + text: menuOption.audioScopeTwentyFrames; + checkable: true + } + MenuItem { + exclusiveGroup: audioScopeFramesGroup; + text: menuOption.audioScopeFiftyFrames; + checkable: true + } + } + MenuItem { + text: menuOption.audioNetworkStats + } + } + } + Menu { + title: "Physics" + MenuItem { + text: menuOption.physicsShowOwned; + checkable: true + } + MenuItem { + text: menuOption.physicsShowHulls; + checkable: true + } + } + MenuItem { + text: menuOption.displayCrashOptions; + checkable: true + } + MenuItem { + text: menuOption.crashInterface + } + MenuItem { + text: menuOption.log + } + MenuItem { + text: menuOption.stats; + checkable: true + } + } +} diff --git a/tests-manual/ui/qml/Stubs.qml b/tests-manual/ui/qml/Stubs.qml new file mode 100644 index 0000000000..8c1465d54c --- /dev/null +++ b/tests-manual/ui/qml/Stubs.qml @@ -0,0 +1,346 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +// Stubs for the global service objects set by Interface.cpp when creating the UI +// This is useful for testing inside Qt creator where these services don't actually exist. +Item { + + Item { + objectName: "offscreenFlags" + property bool navigationFocused: false + } + + Item { + objectName: "urlHandler" + function fixupUrl(url) { return url; } + function canHandleUrl(url) { return false; } + function handleUrl(url) { return true; } + } + + Item { + objectName: "Account" + function isLoggedIn() { return true; } + function getUsername() { return "Jherico"; } + } + + Item { + objectName: "GL" + property string vendor: "" + } + + Item { + objectName: "ApplicationCompositor" + property bool reticleOverDesktop: true + } + + Item { + objectName: "Controller" + function getRecommendedOverlayRect() { + return Qt.rect(0, 0, 1920, 1080); + } + } + + Item { + objectName: "Preferences" + // List of categories obtained by logging categories as they are added in Interface in Preferences::addPreference(). + property var categories: [ + "Avatar Basics", "Snapshots", "Scripts", "Privacy", "Level of Detail Tuning", "Avatar Tuning", "Avatar Camera", + "Audio", "Octree", "HMD", "Sixense Controllers", "Graphics" + ] + } + + Item { + objectName: "ScriptDiscoveryService" + //property var scriptsModelFilter: scriptsModel + signal scriptCountChanged() + property var _runningScripts:[ + { name: "wireFrameTest.js", url: "foo/wireframetest.js", path: "foo/wireframetest.js", local: true }, + { name: "edit.js", url: "foo/edit.js", path: "foo/edit.js", local: false }, + { name: "listAllScripts.js", url: "foo/listAllScripts.js", path: "foo/listAllScripts.js", local: false }, + { name: "users.js", url: "foo/users.js", path: "foo/users.js", local: false }, + ] + + function getRunning() { + return _runningScripts; + } + } + + Item { + objectName: "HMD" + property bool active: false + } + + Item { + id: menuHelper + objectName: "MenuHelper" + + Component { + id: modelMaker + ListModel { } + } + + function toModel(menu, parent) { + if (!parent) { parent = menuHelper } + var result = modelMaker.createObject(parent); + if (menu.type !== MenuItemType.Menu) { + console.warn("Not a menu: " + menu); + return result; + } + + var items = menu.items; + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + switch (item.type) { + case 2: + result.append({"name": item.title, "item": item}) + break; + case 1: + result.append({"name": item.text, "item": item}) + break; + case 0: + result.append({"name": "", "item": item}) + break; + } + } + return result; + } + + } + + Item { + objectName: "Desktop" + + property string _OFFSCREEN_ROOT_OBJECT_NAME: "desktopRoot"; + property string _OFFSCREEN_DIALOG_OBJECT_NAME: "topLevelWindow"; + + + function findChild(item, name) { + for (var i = 0; i < item.children.length; ++i) { + if (item.children[i].objectName === name) { + return item.children[i]; + } + } + return null; + } + + function findParent(item, name) { + while (item) { + if (item.objectName === name) { + return item; + } + item = item.parent; + } + return null; + } + + function findDialog(item) { + item = findParent(item, _OFFSCREEN_DIALOG_OBJECT_NAME); + return item; + } + + function closeDialog(item) { + item = findDialog(item); + if (item) { + item.visible = false + } else { + console.warn("Could not find top level dialog") + } + } + + function getDesktop(item) { + while (item) { + if (item.desktopRoot) { + break; + } + item = item.parent; + } + return item + } + + function raise(item) { + var desktop = getDesktop(item); + if (desktop) { + desktop.raise(item); + } + } + } + + Menu { + id: root + objectName: "rootMenu" + + Menu { + title: "Audio" + } + + Menu { + title: "Avatar" + } + + Menu { + title: "Display" + ExclusiveGroup { id: displayMode } + Menu { + title: "More Stuff" + + Menu { title: "Empty" } + + MenuItem { + text: "Do Nothing" + onTriggered: console.log("Nothing") + } + } + MenuItem { + text: "Oculus" + exclusiveGroup: displayMode + checkable: true + } + MenuItem { + text: "OpenVR" + exclusiveGroup: displayMode + checkable: true + } + MenuItem { + text: "OSVR" + exclusiveGroup: displayMode + checkable: true + } + MenuItem { + text: "2D Screen" + exclusiveGroup: displayMode + checkable: true + checked: true + } + MenuItem { + text: "3D Screen (Active)" + exclusiveGroup: displayMode + checkable: true + } + MenuItem { + text: "3D Screen (Passive)" + exclusiveGroup: displayMode + checkable: true + } + } + + Menu { + title: "View" + Menu { + title: "Camera Mode" + ExclusiveGroup { id: cameraMode } + MenuItem { + exclusiveGroup: cameraMode + text: "First Person"; + onTriggered: console.log(text + " checked " + checked) + checkable: true + checked: true + } + MenuItem { + exclusiveGroup: cameraMode + text: "Third Person"; + onTriggered: console.log(text) + checkable: true + } + MenuItem { + exclusiveGroup: cameraMode + text: "Independent Mode"; + onTriggered: console.log(text) + checkable: true + } + MenuItem { + exclusiveGroup: cameraMode + text: "Entity Mode"; + onTriggered: console.log(text) + enabled: false + checkable: true + } + MenuItem { + exclusiveGroup: cameraMode + text: "Fullscreen Mirror"; + onTriggered: console.log(text) + checkable: true + } + } + } + + Menu { + title: "Edit" + + MenuItem { + text: "Undo" + shortcut: "Ctrl+Z" + enabled: false + onTriggered: console.log(text) + } + + MenuItem { + text: "Redo" + shortcut: "Ctrl+Shift+Z" + enabled: false + onTriggered: console.log(text) + } + + MenuSeparator { } + + MenuItem { + text: "Cut" + shortcut: "Ctrl+X" + onTriggered: console.log(text) + } + + MenuItem { + text: "Copy" + shortcut: "Ctrl+C" + onTriggered: console.log(text) + } + + MenuItem { + text: "Paste" + shortcut: "Ctrl+V" + visible: false + onTriggered: console.log("Paste") + } + } + + Menu { + title: "Navigate" + } + + Menu { + title: "Market" + } + + Menu { + title: "Settings" + } + + Menu { + title: "Developer" + } + + Menu { + title: "Quit" + } + + Menu { + title: "File" + + Action { + id: login + text: "Login" + } + + Action { + id: quit + text: "Quit" + shortcut: "Ctrl+Q" + onTriggered: Qt.quit(); + } + + MenuItem { action: quit } + MenuItem { action: login } + } + } + +} + diff --git a/tests-manual/ui/qml/TestControllers.qml b/tests-manual/ui/qml/TestControllers.qml new file mode 100644 index 0000000000..e9a7fb49e5 --- /dev/null +++ b/tests-manual/ui/qml/TestControllers.qml @@ -0,0 +1,160 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "controller" +import "controls" as HifiControls +import "styles" + +HifiControls.VrDialog { + id: root + HifiConstants { id: hifi } + title: "Controller Test" + resizable: true + contentImplicitWidth: clientArea.implicitWidth + contentImplicitHeight: clientArea.implicitHeight + backgroundColor: "beige" + + property var actions: Controller.Actions + property var standard: Controller.Standard + property var hydra: null + property var testMapping: null + property bool testMappingEnabled: false + property var xbox: null + + function buildMapping() { + testMapping = Controller.newMapping(); + testMapping.fromQml(standard.RY).invert().toQml(actions.Pitch); + testMapping.fromQml(function(){ + return Math.sin(Date.now() / 250); + }).toQml(actions.Yaw); + //testMapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw); + // Step yaw takes a number of degrees + testMapping.fromQml(standard.LB).pulse(0.10).invert().scale(40.0).toQml(actions.StepYaw); + testMapping.fromQml(standard.RB).pulse(0.10).scale(15.0).toQml(actions.StepYaw); + testMapping.fromQml(standard.RX).scale(15.0).toQml(actions.StepYaw); + } + + function toggleMapping() { + testMapping.enable(!testMappingEnabled); + testMappingEnabled = !testMappingEnabled; + } + + Component.onCompleted: { + var xboxRegex = /^GamePad/; + var hydraRegex = /^Hydra/; + for (var prop in Controller.Hardware) { + if(xboxRegex.test(prop)) { + root.xbox = Controller.Hardware[prop] + print("found xbox") + continue + } + if (hydraRegex.test(prop)) { + root.hydra = Controller.Hardware[prop] + print("found hydra") + continue + } + } + } + + Column { + id: clientArea + spacing: 12 + x: root.clientX + y: root.clientY + + Row { + spacing: 8 + + Button { + text: !root.testMapping ? "Build Mapping" : (root.testMappingEnabled ? "Disable Mapping" : "Enable Mapping") + onClicked: { + + if (!root.testMapping) { + root.buildMapping() + } else { + root.toggleMapping(); + } + } + } + } + + Row { + Standard { device: root.standard; label: "Standard"; width: 180 } + } + + Row { + spacing: 8 + Xbox { device: root.xbox; label: "XBox"; width: 180 } + Hydra { device: root.hydra; width: 180 } + } + + Row { + spacing: 4 + ScrollingGraph { + controlId: Controller.Actions.Yaw + label: "Yaw" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.YawLeft + label: "Yaw Left" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.YawRight + label: "Yaw Right" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.StepYaw + label: "StepYaw" + min: -20.0 + max: 20.0 + size: 64 + } + } + + Row { + ScrollingGraph { + controlId: Controller.Actions.TranslateZ + label: "TranslateZ" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.Forward + label: "Forward" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.Backward + label: "Backward" + min: -2.0 + max: 2.0 + size: 64 + } + + } + } +} // dialog + + + + + diff --git a/tests-manual/ui/qml/TestDialog.qml b/tests-manual/ui/qml/TestDialog.qml new file mode 100644 index 0000000000..e6675b7282 --- /dev/null +++ b/tests-manual/ui/qml/TestDialog.qml @@ -0,0 +1,94 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.3 +import "controls" + +VrDialog { + title: "Test Dialog" + id: testDialog + objectName: "TestDialog" + width: 512 + height: 512 + animationDuration: 200 + + onEnabledChanged: { + if (enabled) { + edit.forceActiveFocus(); + } + } + + Item { + id: clientArea + // The client area + anchors.fill: parent + anchors.margins: parent.margins + anchors.topMargin: parent.topMargin + + Rectangle { + property int d: 100 + id: square + objectName: "testRect" + width: d + height: d + anchors.centerIn: parent + color: "red" + NumberAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; } + } + + + TextEdit { + id: edit + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.right: parent.right + anchors.rightMargin: 12 + clip: true + text: "test edit" + anchors.top: parent.top + anchors.topMargin: 12 + } + + Button { + x: 128 + y: 192 + text: "Test" + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + anchors.right: parent.right + anchors.rightMargin: 12 + onClicked: { + console.log("Click"); + + if (square.visible) { + square.visible = false + } else { + square.visible = true + } + } + } + + Button { + id: customButton2 + y: 192 + text: "Move" + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + onClicked: { + onClicked: testDialog.x == 0 ? testDialog.x = 200 : testDialog.x = 0 + } + } + + Keys.onPressed: { + console.log("Key " + event.key); + switch (event.key) { + case Qt.Key_Q: + if (Qt.ControlModifier == event.modifiers) { + event.accepted = true; + break; + } + } + } + } +} diff --git a/tests-manual/ui/qml/TestMenu.qml b/tests-manual/ui/qml/TestMenu.qml new file mode 100644 index 0000000000..fe8a26e234 --- /dev/null +++ b/tests-manual/ui/qml/TestMenu.qml @@ -0,0 +1,10 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import Hifi 1.0 + +// Currently for testing a pure QML replacement menu +Item { + Menu { + objectName: "rootMenu"; + } +} diff --git a/tests-manual/ui/qml/TestRoot.qml b/tests-manual/ui/qml/TestRoot.qml new file mode 100644 index 0000000000..bd38c696bf --- /dev/null +++ b/tests-manual/ui/qml/TestRoot.qml @@ -0,0 +1,43 @@ +import Hifi 1.0 +import QtQuick 2.3 +import QtQuick.Controls 1.3 +// Import local folder last so that our own control customizations override +// the built in ones +import "controls" + +Root { + id: root + anchors.fill: parent + onParentChanged: { + forceActiveFocus(); + } + Button { + id: messageBox + anchors.right: createDialog.left + anchors.rightMargin: 24 + anchors.bottom: parent.bottom + anchors.bottomMargin: 24 + text: "Message" + onClicked: { + console.log("Foo") + root.information("a") + console.log("Bar") + } + } + Button { + id: createDialog + anchors.right: parent.right + anchors.rightMargin: 24 + anchors.bottom: parent.bottom + anchors.bottomMargin: 24 + text: "Create" + onClicked: { + root.loadChild("MenuTest.qml"); + } + } + + Keys.onPressed: { + console.log("Key press root") + } +} + diff --git a/tests-manual/ui/qml/controlDemo/ButtonPage.qml b/tests-manual/ui/qml/controlDemo/ButtonPage.qml new file mode 100644 index 0000000000..0ed7e2d6ad --- /dev/null +++ b/tests-manual/ui/qml/controlDemo/ButtonPage.qml @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 + +ScrollView { + id: page + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Item { + id: content + + width: Math.max(page.viewport.width, grid.implicitWidth + 2 * grid.rowSpacing) + height: Math.max(page.viewport.height, grid.implicitHeight + 2 * grid.columnSpacing) + + GridLayout { + id: grid + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: grid.rowSpacing + anchors.rightMargin: grid.rowSpacing + anchors.topMargin: grid.columnSpacing + + columns: page.width < page.height ? 1 : 2 + + GroupBox { + title: "Button" + Layout.fillWidth: true + Layout.columnSpan: grid.columns + RowLayout { + anchors.fill: parent + Button { text: "OK"; isDefault: true } + Button { text: "Cancel" } + Item { Layout.fillWidth: true } + Button { + text: "Attach" + menu: Menu { + MenuItem { text: "Image" } + MenuItem { text: "Document" } + } + } + } + } + + GroupBox { + title: "CheckBox" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + CheckBox { text: "E-mail"; checked: true } + CheckBox { text: "Calendar"; checked: true } + CheckBox { text: "Contacts" } + } + } + + GroupBox { + title: "RadioButton" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + ExclusiveGroup { id: radioGroup } + RadioButton { text: "Portrait"; exclusiveGroup: radioGroup } + RadioButton { text: "Landscape"; exclusiveGroup: radioGroup } + RadioButton { text: "Automatic"; exclusiveGroup: radioGroup; checked: true } + } + } + + GroupBox { + title: "Switch" + Layout.fillWidth: true + Layout.columnSpan: grid.columns + ColumnLayout { + anchors.fill: parent + RowLayout { + Label { text: "Wi-Fi"; Layout.fillWidth: true } + Switch { checked: true } + } + RowLayout { + Label { text: "Bluetooth"; Layout.fillWidth: true } + Switch { checked: false } + } + } + } + } + } +} diff --git a/tests-manual/ui/qml/controlDemo/InputPage.qml b/tests-manual/ui/qml/controlDemo/InputPage.qml new file mode 100644 index 0000000000..cb1878d023 --- /dev/null +++ b/tests-manual/ui/qml/controlDemo/InputPage.qml @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 + +ScrollView { + id: page + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Item { + id: content + + width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) + height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) + + ColumnLayout { + id: column + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: column.spacing + + GroupBox { + title: "TextField" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + TextField { placeholderText: "..."; Layout.fillWidth: true; z: 1 } + TextField { placeholderText: "Password"; echoMode: TextInput.Password; Layout.fillWidth: true } + } + } + + GroupBox { + title: "ComboBox" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + ComboBox { + model: Qt.fontFamilies() + Layout.fillWidth: true + } + ComboBox { + editable: true + model: ListModel { + id: listModel + ListElement { text: "Apple" } + ListElement { text: "Banana" } + ListElement { text: "Coconut" } + ListElement { text: "Orange" } + } + onAccepted: { + if (find(currentText) === -1) { + listModel.append({text: editText}) + currentIndex = find(editText) + } + } + Layout.fillWidth: true + } + } + } + + GroupBox { + title: "SpinBox" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + SpinBox { value: 99; Layout.fillWidth: true; z: 1 } + SpinBox { decimals: 2; Layout.fillWidth: true } + } + } + } + } +} diff --git a/tests-manual/ui/qml/controlDemo/ProgressPage.qml b/tests-manual/ui/qml/controlDemo/ProgressPage.qml new file mode 100644 index 0000000000..a1fa596f79 --- /dev/null +++ b/tests-manual/ui/qml/controlDemo/ProgressPage.qml @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 + +ScrollView { + id: page + + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + + Item { + id: content + + width: Math.max(page.viewport.width, column.implicitWidth + 2 * column.spacing) + height: Math.max(page.viewport.height, column.implicitHeight + 2 * column.spacing) + + ColumnLayout { + id: column + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: column.spacing + + GroupBox { + title: "ProgressBar" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + ProgressBar { indeterminate: true; Layout.fillWidth: true } + ProgressBar { value: slider.value; Layout.fillWidth: true } + } + } + + GroupBox { + title: "Slider" + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + Slider { id: slider; value: 0.5; Layout.fillWidth: true } + } + } + + GroupBox { + title: "BusyIndicator" + Layout.fillWidth: true + BusyIndicator { running: true } + } + } + } +} diff --git a/tests-manual/ui/qml/controlDemo/main.qml b/tests-manual/ui/qml/controlDemo/main.qml new file mode 100644 index 0000000000..168b9fb291 --- /dev/null +++ b/tests-manual/ui/qml/controlDemo/main.qml @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Quick Controls module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls 1.2 +import "qml/UI.js" as UI +import "qml" +//import "/Users/bdavis/Git/hifi/interface/resources/qml" + +Item { + anchors.fill: parent + visible: true + //title: "Qt Quick Controls Gallery" + + MessageDialog { + id: aboutDialog + icon: StandardIcon.Information + title: "About" + text: "Qt Quick Controls Gallery" + informativeText: "This example demonstrates most of the available Qt Quick Controls." + } + + Action { + id: copyAction + text: "&Copy" + shortcut: StandardKey.Copy + iconName: "edit-copy" + enabled: (!!activeFocusItem && !!activeFocusItem["copy"]) + onTriggered: activeFocusItem.copy() + } + + Action { + id: cutAction + text: "Cu&t" + shortcut: StandardKey.Cut + iconName: "edit-cut" + enabled: (!!activeFocusItem && !!activeFocusItem["cut"]) + onTriggered: activeFocusItem.cut() + } + + Action { + id: pasteAction + text: "&Paste" + shortcut: StandardKey.Paste + iconName: "edit-paste" + enabled: (!!activeFocusItem && !!activeFocusItem["paste"]) + onTriggered: activeFocusItem.paste() + } + +// toolBar: ToolBar { +// RowLayout { +// anchors.fill: parent +// anchors.margins: spacing +// Label { +// text: UI.label +// } +// Item { Layout.fillWidth: true } +// CheckBox { +// id: enabler +// text: "Enabled" +// checked: true +// } +// } +// } + +// menuBar: MenuBar { +// Menu { +// title: "&File" +// MenuItem { +// text: "E&xit" +// shortcut: StandardKey.Quit +// onTriggered: Qt.quit() +// } +// } +// Menu { +// title: "&Edit" +// visible: tabView.currentIndex == 2 +// MenuItem { action: cutAction } +// MenuItem { action: copyAction } +// MenuItem { action: pasteAction } +// } +// Menu { +// title: "&Help" +// MenuItem { +// text: "About..." +// onTriggered: aboutDialog.open() +// } +// } +// } + + TabView { + id: tabView + + anchors.fill: parent + anchors.margins: UI.margin + tabPosition: UI.tabPosition + + Layout.minimumWidth: 360 + Layout.minimumHeight: 360 + Layout.preferredWidth: 480 + Layout.preferredHeight: 640 + + Tab { + title: "Buttons" + ButtonPage { + enabled: enabler.checked + } + } + Tab { + title: "Progress" + ProgressPage { + enabled: enabler.checked + } + } + Tab { + title: "Input" + InputPage { + enabled: enabler.checked + } + } + } +} diff --git a/tests-manual/ui/qml/main.qml b/tests-manual/ui/qml/main.qml new file mode 100644 index 0000000000..607bd624a1 --- /dev/null +++ b/tests-manual/ui/qml/main.qml @@ -0,0 +1,461 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 as OriginalDialogs +import Qt.labs.settings 1.0 + +import "../../../interface/resources/qml" +//import "../../../interface/resources/qml/windows" +import "../../../interface/resources/qml/windows" +import "../../../interface/resources/qml/dialogs" +import "../../../interface/resources/qml/hifi" +import "../../../interface/resources/qml/hifi/dialogs" +import "../../../interface/resources/qml/styles-uit" + +ApplicationWindow { + id: appWindow + objectName: "MainWindow" + visible: true + width: 1280 + height: 800 + title: qsTr("Scratch App") + toolBar: Row { + id: testButtons + anchors { margins: 8; left: parent.left; top: parent.top } + spacing: 8 + property int count: 0 + + property var tabs: []; + property var urls: []; + property var toolbar; + property var lastButton; + + Button { + text: HMD.active ? "Disable HMD" : "Enable HMD" + onClicked: HMD.active = !HMD.active + } + + Button { + text: desktop.hmdHandMouseActive ? "Disable HMD HandMouse" : "Enable HMD HandMouse" + onClicked: desktop.hmdHandMouseActive = !desktop.hmdHandMouseActive + } + + // Window visibility + Button { + text: "toggle desktop" + onClicked: desktop.togglePinned() + } + + Button { + text: "Create Toolbar" + onClicked: testButtons.toolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); + } + + Button { + text: "Toggle Toolbar Direction" + onClicked: testButtons.toolbar.horizontal = !testButtons.toolbar.horizontal + } + + Button { + readonly property var icons: [ + "edit-01.svg", + "model-01.svg", + "cube-01.svg", + "sphere-01.svg", + "light-01.svg", + "text-01.svg", + "web-01.svg", + "zone-01.svg", + "particle-01.svg", + ] + property int iconIndex: 0 + readonly property string toolIconUrl: "../../../../../scripts/system/assets/images/tools/" + text: "Create Button" + onClicked: { + var name = icons[iconIndex]; + var url = toolIconUrl + name; + iconIndex = (iconIndex + 1) % icons.length; + var button = testButtons.lastButton = testButtons.toolbar.addButton({ + imageURL: url, + objectName: name, + subImage: { + y: 50, + }, + alpha: 0.9 + }); + + button.clicked.connect(function(){ + console.log("Clicked on button " + button.imageURL + " alpha " + button.alpha) + }); + } + } + + Button { + text: "Toggle Button Visible" + onClicked: testButtons.lastButton.visible = !testButtons.lastButton.visible + } + + // Error alerts + /* + Button { + // Message without title. + text: "Show Error" + onClicked: { + var messageBox = desktop.messageBox({ + text: "Diagnostic cycle will be complete in 30 seconds", + icon: hifi.icons.critical, + }); + messageBox.selected.connect(function(button) { + console.log("You clicked " + button) + }) + } + } + Button { + // detailedText is not currently used anywhere in Interface but it is easier to leave in and style good enough. + text: "Show Long Error" + onClicked: { + desktop.messageBox({ + informativeText: "Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds ", + text: "Baloney", + icon: hifi.icons.warning, + detailedText: "sakjd;laskj dksa;dl jka;lsd j;lkjas ;dlkaj s;dlakjd ;alkjda; slkjda; lkjda;lksjd ;alksjd; alksjd ;alksjd; alksjd; alksdjas;ldkjas;lkdja ;kj ;lkasjd; lkj as;dlka jsd;lka jsd;laksjd a" + }); + } + } + */ + + // query + /* + // There is no such desktop.queryBox() function; may need to update test to cover QueryDialog.qml? + Button { + text: "Show Query" + onClicked: { + var queryBox = desktop.queryBox({ + text: "Have you stopped beating your wife?", + placeholderText: "Are you sure?", + // icon: hifi.icons.critical, + }); + queryBox.selected.connect(function(result) { + console.log("User responded with " + result); + }); + + queryBox.canceled.connect(function() { + console.log("User cancelled query box "); + }) + } + } + */ + + // Browser + /* + Button { + text: "Open Browser" + onClicked: builder.createObject(desktop); + property var builder: Component { + Browser {} + } + } + */ + + + // file dialog + /* + + Button { + text: "Open Directory" + property var builder: Component { + FileDialog { selectDirectory: true } + } + + onClicked: { + var fileDialog = builder.createObject(desktop); + fileDialog.canceled.connect(function(){ + console.log("Cancelled") + }) + fileDialog.selectedFile.connect(function(file){ + console.log("Selected " + file) + }) + } + } + Button { + text: "Open File" + property var builder: Component { + FileDialog { + title: "Open File" + filter: "All Files (*.*)" + //filter: "HTML files (*.html);;Other(*.png)" + } + } + + onClicked: { + var fileDialog = builder.createObject(desktop); + fileDialog.canceled.connect(function(){ + console.log("Cancelled") + }) + fileDialog.selectedFile.connect(function(file){ + console.log("Selected " + file) + }) + } + } + */ + + // tabs + /* + Button { + text: "Add Tab" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo" }); + desktop.toolWindow.showTabForUrl("Foo", true); + } + } + + Button { + text: "Add Tab 2" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo 2" }); + desktop.toolWindow.showTabForUrl("Foo 2", true); + } + } + + Button { + text: "Add Tab 3" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo 3" }); + desktop.toolWindow.showTabForUrl("Foo 3", true); + } + } + + Button { + text: "Destroy Tab" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.removeTabForUrl("Foo"); + } + } + */ + + // Hifi specific stuff + /* + Button { + // Shows the dialog with preferences sections but not each section's preference items + // because Preferences.preferencesByCategory() method is not stubbed out. + text: "Settings > General..." + property var builder: Component { + GeneralPreferencesDialog { } + } + onClicked: { + var runningScripts = builder.createObject(desktop); + } + } + + Button { + text: "Running Scripts" + property var builder: Component { + RunningScripts { } + } + onClicked: { + var runningScripts = builder.createObject(desktop); + } + } + + Button { + text: "Attachments" + property var builder: Component { + AttachmentsDialog { } + } + onClicked: { + var attachmentsDialog = builder.createObject(desktop); + } + } + Button { + // Replicates message box that pops up after selecting new avatar. Includes title. + text: "Confirm Avatar" + onClicked: { + var messageBox = desktop.messageBox({ + title: "Set Avatar", + text: "Would you like to use 'Albert' for your avatar?", + icon: hifi.icons.question, // Test question icon + //icon: hifi.icons.information, // Test informaton icon + //icon: hifi.icons.warning, // Test warning icon + //icon: hifi.icons.critical, // Test critical icon + //icon: hifi.icons.none, // Test no icon + buttons: OriginalDialogs.StandardButton.Ok + OriginalDialogs.StandardButton.Cancel, + defaultButton: OriginalDialogs.StandardButton.Ok + }); + messageBox.selected.connect(function(button) { + console.log("You clicked " + button) + }) + } + } + */ + // bookmarks + /* + Button { + text: "Bookmark Location" + onClicked: { + desktop.inputDialog({ + title: "Bookmark Location", + icon: hifi.icons.placemark, + label: "Name" + }); + } + } + Button { + text: "Delete Bookmark" + onClicked: { + desktop.inputDialog({ + title: "Delete Bookmark", + icon: hifi.icons.placemark, + label: "Select the bookmark to delete", + items: ["Bookmark A", "Bookmark B", "Bookmark C"] + }); + } + } + Button { + text: "Duplicate Bookmark" + onClicked: { + desktop.messageBox({ + title: "Duplicate Bookmark", + icon: hifi.icons.warning, + text: "The bookmark name you entered alread exists in yoru list.", + informativeText: "Would you like to overwrite it?", + buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, + defaultButton: OriginalDialogs.StandardButton.Yes + }); + } + } + */ + + } + + + HifiConstants { id: hifi } + + Desktop { + id: desktop + anchors.fill: parent + + //rootMenu: StubMenu { id: rootMenu } + //Component.onCompleted: offscreenWindow = appWindow + + /* + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: desktop.popupMenu(Qt.vector2d(mouseX, mouseY)); + } + */ + + Browser { + url: "http://s3.amazonaws.com/DreamingContent/testUiDelegates.html" + } + + + Window { + id: blue + closable: true + visible: true + resizable: true + destroyOnHidden: false + title: "Blue" + + width: 100; height: 100 + x: 1280 / 2; y: 720 / 2 + Settings { + category: "TestWindow.Blue" + property alias x: blue.x + property alias y: blue.y + property alias width: blue.width + property alias height: blue.height + } + + Rectangle { + anchors.fill: parent + visible: true + color: "blue" + Component.onDestruction: console.log("Blue destroyed") + } + } + + Window { + id: green + closable: true + visible: true + resizable: true + title: "Green" + destroyOnHidden: false + + width: 100; height: 100 + x: 1280 / 2; y: 720 / 2 + Settings { + category: "TestWindow.Green" + property alias x: green.x + property alias y: green.y + property alias width: green.width + property alias height: green.height + } + + Rectangle { + anchors.fill: parent + visible: true + color: "green" + Component.onDestruction: console.log("Green destroyed") + } + } + +/* + Rectangle { width: 100; height: 100; x: 100; y: 100; color: "#00f" } + + Window { + id: green + alwaysOnTop: true + frame: HiddenFrame{} + hideBackground: true + closable: true + visible: true + resizable: false + x: 1280 / 2; y: 720 / 2 + width: 100; height: 100 + Rectangle { + color: "#0f0" + width: green.width; + height: green.height; + } + } + */ + +/* + Window { + id: yellow + closable: true + visible: true + resizable: true + x: 100; y: 100 + width: 100; height: 100 + Rectangle { + anchors.fill: parent + visible: true + color: "yellow" + } + } +*/ + } + + Action { + id: openBrowserAction + text: "Open Browser" + shortcut: "Ctrl+Shift+X" + onTriggered: { + builder.createObject(desktop); + } + property var builder: Component { + ModelBrowserDialog{} + } + } +} + + + + diff --git a/tests-manual/ui/qmlscratch.pro b/tests-manual/ui/qmlscratch.pro index 3287180d26..5c9b91ee52 100644 --- a/tests-manual/ui/qmlscratch.pro +++ b/tests-manual/ui/qmlscratch.pro @@ -4,10 +4,34 @@ QT += gui qml quick xml webengine widgets CONFIG += c++11 -SOURCES += src/main.cpp +SOURCES += src/main.cpp \ + ../../libraries/ui/src/FileDialogHelper.cpp + +HEADERS += \ + ../../libraries/ui/src/FileDialogHelper.h # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = ../../interface/resources/qml + DISTFILES += \ - qml/*.qml + qml/*.qml \ + ../../interface/resources/QtWebEngine/UIDelegates/original/*.qml \ + ../../interface/resources/QtWebEngine/UIDelegates/*.qml \ + ../../interface/resources/qml/*.qml \ + ../../interface/resources/qml/controls/*.qml \ + ../../interface/resources/qml/controls-uit/*.qml \ + ../../interface/resources/qml/dialogs/*.qml \ + ../../interface/resources/qml/dialogs/fileDialog/*.qml \ + ../../interface/resources/qml/dialogs/preferences/*.qml \ + ../../interface/resources/qml/dialogs/messageDialog/*.qml \ + ../../interface/resources/qml/desktop/*.qml \ + ../../interface/resources/qml/menus/*.qml \ + ../../interface/resources/qml/styles/*.qml \ + ../../interface/resources/qml/styles-uit/*.qml \ + ../../interface/resources/qml/windows/*.qml \ + ../../interface/resources/qml/hifi/*.qml \ + ../../interface/resources/qml/hifi/toolbars/*.qml \ + ../../interface/resources/qml/hifi/dialogs/*.qml \ + ../../interface/resources/qml/hifi/dialogs/preferences/*.qml \ + ../../interface/resources/qml/hifi/overlays/*.qml diff --git a/tests-manual/ui/src/main.cpp b/tests-manual/ui/src/main.cpp index a5061f4d01..312b5f3823 100644 --- a/tests-manual/ui/src/main.cpp +++ b/tests-manual/ui/src/main.cpp @@ -3,31 +3,88 @@ #include #include +#include "../../../libraries/ui/src/FileDialogHelper.h" + + +class Preference : public QObject { + Q_OBJECT + Q_PROPERTY(QString category READ getCategory() CONSTANT) + Q_PROPERTY(QString name READ getName() CONSTANT) + Q_PROPERTY(Type type READ getType() CONSTANT) + Q_ENUMS(Type) +public: + enum Type { + Editable, + Browsable, + Spinner, + Checkbox, + }; + + Preference(QObject* parent = nullptr) : QObject(parent) {} + + Preference(const QString& category, const QString& name, QObject* parent = nullptr) + : QObject(parent), _category(category), _name(name) { } + const QString& getCategory() const { return _category; } + const QString& getName() const { return _name; } + virtual Type getType() { return Editable; } + +protected: + const QString _category; + const QString _name; +}; + +class Reticle : public QObject { + Q_OBJECT + Q_PROPERTY(QPoint position READ getPosition CONSTANT) +public: + + Reticle(QObject* parent) : QObject(parent) { + } + + QPoint getPosition() { + if (!_window) { + return QPoint(0, 0); + } + return _window->mapFromGlobal(QCursor::pos()); + } + + void setWindow(QWindow* window) { + _window = window; + } + +private: + QWindow* _window{nullptr}; +}; + QString getRelativeDir(const QString& relativePath = ".") { - QDir path(__FILE__); - path.cdUp(); - path.cdUp(); + QDir path(__FILE__); path.cdUp(); auto result = path.absoluteFilePath(relativePath); result = path.cleanPath(result) + "/"; return result; } -QString getResourcesDir() { - return getRelativeDir("../../interface/resources"); +QString getTestQmlDir() { + return getRelativeDir("../qml"); } -QString getQmlDir() { - return getRelativeDir("../../interface/resources/qml"); +QString getInterfaceQmlDir() { + return getRelativeDir("/"); } -QString getScriptsDir() { - return getRelativeDir("../../scripts"); + +void setChild(QQmlApplicationEngine& engine, const char* name) { + for (auto obj : engine.rootObjects()) { + auto child = obj->findChild(QString(name)); + if (child) { + engine.rootContext()->setContextProperty(name, child); + return; + } + } + qWarning() << "Could not find object named " << name; } void addImportPath(QQmlApplicationEngine& engine, const QString& relativePath, bool insert = false) { QString resolvedPath = getRelativeDir(relativePath); - - qDebug() << "adding import path: " << QDir::toNativeSeparators(resolvedPath); engine.addImportPath(resolvedPath); } @@ -36,24 +93,44 @@ int main(int argc, char *argv[]) { app.setOrganizationName("Some Company"); app.setOrganizationDomain("somecompany.com"); app.setApplicationName("Amazing Application"); + QDir::setCurrent(getRelativeDir("..")); - auto scriptsDir = getScriptsDir(); - auto resourcesDir = getResourcesDir(); + QtWebEngine::initialize(); + qmlRegisterType("Hifi", 1, 0, "Preference"); QQmlApplicationEngine engine; - addImportPath(engine, "."); addImportPath(engine, "qml"); - addImportPath(engine, resourcesDir); - addImportPath(engine, resourcesDir + "/qml"); - addImportPath(engine, scriptsDir); - addImportPath(engine, scriptsDir + "/developer/tests"); + addImportPath(engine, "../../interface/resources/qml"); + addImportPath(engine, "../../interface/resources"); + engine.load(QUrl(QStringLiteral("qml/Stubs.qml"))); - QFontDatabase::addApplicationFont(resourcesDir + "/fonts/FiraSans-Regular.ttf"); - QFontDatabase::addApplicationFont(resourcesDir + "/fonts/FiraSans-SemiBold.ttf"); - QFontDatabase::addApplicationFont(resourcesDir + "/fonts/hifi-glyphs.ttf"); + setChild(engine, "offscreenFlags"); + setChild(engine, "Account"); + setChild(engine, "ApplicationCompositor"); + setChild(engine, "Controller"); + setChild(engine, "Desktop"); + setChild(engine, "ScriptDiscoveryService"); + setChild(engine, "HMD"); + setChild(engine, "GL"); + setChild(engine, "MenuHelper"); + setChild(engine, "Preferences"); + setChild(engine, "urlHandler"); + engine.rootContext()->setContextProperty("DebugQML", true); + engine.rootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); - auto url = getRelativeDir(".") + "qml/ControlsGalleryWindow.qml"; - - engine.load(url); + //engine.load(QUrl(QStringLiteral("qrc:/qml/gallery/main.qml"))); + engine.load(QUrl(QStringLiteral("qml/main.qml"))); + for (QObject* rootObject : engine.rootObjects()) { + if (rootObject->objectName() == "MainWindow") { + Reticle* reticle = new Reticle(rootObject); + reticle->setWindow((QWindow*)rootObject); + engine.rootContext()->setContextProperty("Reticle", reticle); + engine.rootContext()->setContextProperty("Window", rootObject); + break; + } + } return app.exec(); } + +#include "main.moc" + diff --git a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml index 753771987a..033039b87d 100644 --- a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml +++ b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml @@ -16,8 +16,8 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 -import stylesUit 1.0 as HifiStylesUit -import controlsUit 1.0 as HifiControlsUit +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit import "qrc:////qml//controls" as HifiControls import "qrc:////qml//hifi" as Hifi From 0d39343be4fee9954ef870c091ea6b6270d0191e Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 23 Oct 2018 11:19:47 -0700 Subject: [PATCH 208/276] changed if statement to switch in preferencesDialog.cpp, also removed print statement --- interface/src/ui/PreferencesDialog.cpp | 35 +++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 92628c23b7..2b8f991174 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -260,20 +260,27 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = [myAvatar]()->int { if (myAvatar->getUserRecenterModel() == MyAvatar::SitStandModelType::Auto) { - return 0; - } else if (myAvatar->getUserRecenterModel() == MyAvatar::SitStandModelType::ForceSit) { - return 1; - } else { - return 2; - }}; - auto setter = [myAvatar](int value) { if (value == 0) { - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); - } else if (value == 1) { - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); - } else { - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); - }}; + auto getter = [myAvatar]()->int { switch (myAvatar->getUserRecenterModel()) { + case MyAvatar::SitStandModelType::Auto: + default: + return 0; + case MyAvatar::SitStandModelType::ForceSit: + return 1; + case MyAvatar::SitStandModelType::DisableHMDLean: + return 2; + }}; + auto setter = [myAvatar](int value) { switch (value) { + case 0: + default: + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); + break; + case 1: + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); + break; + case 2: + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); + break; + }}; auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Disable Recenter", getter, setter); QStringList items; items << "Auto - turns on avatar leaning when standing in real world" << "Seated - disables all avatar leaning while sitting in real world" << "Disabled - allows avatar sitting on the floor [Experimental]"; From fa67e1b269fa2c569e478fc68b42b0c8f7377dc6 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 23 Oct 2018 11:47:50 -0700 Subject: [PATCH 209/276] Remove logs and magic numbers --- interface/src/avatar/AvatarManager.cpp | 4 ---- libraries/animation/src/Rig.cpp | 5 +++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e12ea67230..dad4c44f4b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -127,19 +127,15 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) { void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) { switch (status) { case AvatarTransit::Status::STARTED: - qDebug() << "START_FRAME"; _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("preTransitAnim"); break; case AvatarTransit::Status::START_TRANSIT: - qDebug() << "START_TRANSIT"; _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("transitAnim"); break; case AvatarTransit::Status::END_TRANSIT: - qDebug() << "END_TRANSIT"; _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("postTransitAnim"); break; case AvatarTransit::Status::ENDED: - qDebug() << "END_FRAME"; _myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("idleAnim"); break; case AvatarTransit::Status::PRE_TRANSIT: diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index d198550d9a..83d375b55a 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1124,17 +1124,18 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons _internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut); if (_networkNode) { _networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut); + const float NETWORK_ANIMATION_BLEND_FRAMES = 6.0f; float alpha = 1.0f; std::shared_ptr clip; if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) { clip = std::dynamic_pointer_cast(_networkNode->findByName("preTransitAnim")); if (clip) { - alpha = (clip->getFrame() - clip->getStartFrame()) / 6.0f; + alpha = (clip->getFrame() - clip->getStartFrame()) / NETWORK_ANIMATION_BLEND_FRAMES; } } else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) { clip = std::dynamic_pointer_cast(_networkNode->findByName("postTransitAnim")); if (clip) { - alpha = (clip->getEndFrame() - clip->getFrame()) / 6.0f; + alpha = (clip->getEndFrame() - clip->getFrame()) / NETWORK_ANIMATION_BLEND_FRAMES; } } if (_sendNetworkNode) { From 78f0afd39ff85a1467c460b4d5d0d9c78bf1dfe3 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 23 Oct 2018 12:13:41 -0700 Subject: [PATCH 210/276] fix crash on shutdown --- libraries/networking/src/ResourceCache.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 076db44ea6..220e487d3a 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -252,7 +252,9 @@ ResourceCache::ResourceCache(QObject* parent) : QObject(parent) { } } -ResourceCache::~ResourceCache() {} +ResourceCache::~ResourceCache() { + clearUnusedResources(); +} void ResourceCache::clearATPAssets() { { From 9bc92cb2a33cf982ff894057a0a65454d6cb7840 Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 23 Oct 2018 12:29:04 -0700 Subject: [PATCH 211/276] removed leftover triggers --- interface/src/avatar/MyAvatar.cpp | 4 +--- interface/src/avatar/MyAvatar.h | 23 ----------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index dd40a748af..78cfed9b30 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -3961,6 +3961,7 @@ void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { setIsSitStandStateLocked(true); break; case MyAvatar::SitStandModelType::Auto: + default: setHMDLeanRecenterEnabled(true); setIsInSittingState(false); setIsSitStandStateLocked(false); @@ -3970,10 +3971,7 @@ void MyAvatar::setUserRecenterModel(MyAvatar::SitStandModelType modelName) { setIsInSittingState(false); setIsSitStandStateLocked(false); break; - default: - break; } - emit userRecenterModelChanged((int)modelName); } void MyAvatar::setIsSitStandStateLocked(bool isLocked) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0f6f9d9411..8bbf163bd8 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1555,29 +1555,6 @@ signals: */ void disableHandTouchForIDChanged(const QUuid& entityID, bool disable); - /**jsdoc - * Triggered when the sit state is enabled or disabled - * @function MyAvatar.sittingEnabledChanged - * @param {boolean} enabled - * @returns {Signal} - */ - void sittingEnabledChanged(bool enabled); - - /**jsdoc - * Triggered when the recenter model is changed - * @function MyAvatar.userRecenterModelChanged - * @param {int} userRecenteringModeltype - * @returns {Signal} - */ - void userRecenterModelChanged(int modelName); - - /**jsdoc - * Triggered when the sit state is enabled or disabled - * @function MyAvatar.sitStandStateLockEnabledChanged - * @param {boolean} enabled - * @returns {Signal} - */ - void sitStandStateLockEnabledChanged(bool enabled); private slots: void leaveDomain(); From 178b8ef37b9dfbccecbb56913378128508277d21 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Tue, 23 Oct 2018 12:55:02 -0700 Subject: [PATCH 212/276] Removed duplicate menu items Removed duplicates: Console... API Debugger --- interface/src/Menu.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 91019975cd..5e1c9412e8 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -788,21 +788,6 @@ Menu::Menu() { // Developer > Show Animation Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AnimStats); - // Developer > Scripting > Console... - addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, - DependencyManager::get().data(), - SLOT(toggleConsole()), - QAction::NoRole, - UNSPECIFIED_POSITION); - - // Developer > Scripting > API Debugger - action = addActionToQMenuAndActionHash(scriptingOptionsMenu, "API Debugger"); - connect(action, &QAction::triggered, [] { - QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); - defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); - DependencyManager::get()->loadScript(defaultScriptsLoc.toString()); - }); - // Developer > Log addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, qApp, SLOT(toggleLogDialog())); From be7a947ddd85689e67374d6301076e4a2c814044 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Tue, 23 Oct 2018 13:25:59 -0700 Subject: [PATCH 213/276] Fix coding-standards issue --- server-console/src/modules/hf-notifications.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index e1ea508e47..3ee2bd13a4 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -160,8 +160,7 @@ function HifiNotifications(config, menuNotificationCallback) { if ((optUrl.protocol === "hifi") || (optUrl.protocol === "hifiapp")) { StartInterface(options.url); _menuNotificationCallback(options.notificationType, false); - } - else { + } else { shell.openExternal(options.url); } }); From 8807b90474cbb79122de3a4a1d3e720306cd4f9e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 23 Oct 2018 13:33:14 -0700 Subject: [PATCH 214/276] fix emitRadiusStart --- .../entities/src/ParticleEffectEntityItem.cpp | 27 ++++++++++--------- .../entities/src/ParticleEffectEntityItem.h | 3 ++- .../particle_explorer/particleExplorer.js | 7 +++++ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 46dcd1b006..acdeac0e93 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -90,10 +90,10 @@ bool operator==(const Properties& a, const Properties& b) { return (a.color == b.color) && (a.alpha == b.alpha) && + (a.radiusStart == b.radiusStart) && (a.radius == b.radius) && (a.spin == b.spin) && (a.rotateWithEntity == b.rotateWithEntity) && - (a.radiusStart == b.radiusStart) && (a.lifespan == b.lifespan) && (a.maxParticles == b.maxParticles) && (a.emission == b.emission) && @@ -117,18 +117,7 @@ bool Properties::valid() const { (alpha.range.start == glm::clamp(alpha.range.start, MINIMUM_ALPHA, MAXIMUM_ALPHA)) && (alpha.range.finish == glm::clamp(alpha.range.finish, MINIMUM_ALPHA, MAXIMUM_ALPHA)) && (alpha.gradient.spread == glm::clamp(alpha.gradient.spread, MINIMUM_ALPHA, MAXIMUM_ALPHA)) && - (lifespan == glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN)) && - (emission.rate == glm::clamp(emission.rate, MINIMUM_EMIT_RATE, MAXIMUM_EMIT_RATE)) && - (emission.speed.target == glm::clamp(emission.speed.target, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && - (emission.speed.spread == glm::clamp(emission.speed.spread, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && - (emission.dimensions == glm::clamp(emission.dimensions, vec3(MINIMUM_EMIT_DIMENSION), vec3(MAXIMUM_EMIT_DIMENSION))) && (radiusStart == glm::clamp(radiusStart, MINIMUM_EMIT_RADIUS_START, MAXIMUM_EMIT_RADIUS_START)) && - (polar.start == glm::clamp(polar.start, MINIMUM_POLAR, MAXIMUM_POLAR)) && - (polar.finish == glm::clamp(polar.finish, MINIMUM_POLAR, MAXIMUM_POLAR)) && - (azimuth.start == glm::clamp(azimuth.start, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) && - (azimuth.finish == glm::clamp(azimuth.finish, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) && - (emission.acceleration.target == glm::clamp(emission.acceleration.target, vec3(MINIMUM_EMIT_ACCELERATION), vec3(MAXIMUM_EMIT_ACCELERATION))) && - (emission.acceleration.spread == glm::clamp(emission.acceleration.spread, vec3(MINIMUM_ACCELERATION_SPREAD), vec3(MAXIMUM_ACCELERATION_SPREAD))) && (radius.gradient.target == glm::clamp(radius.gradient.target, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.start == glm::clamp(radius.range.start, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.finish == glm::clamp(radius.range.finish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && @@ -136,7 +125,19 @@ bool Properties::valid() const { (spin.gradient.target == glm::clamp(spin.gradient.target, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && (spin.range.start == glm::clamp(spin.range.start, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && (spin.range.finish == glm::clamp(spin.range.finish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && - (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)); + (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (lifespan == glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN)) && + (maxParticles == glm::clamp(maxParticles, MINIMUM_MAX_PARTICLES, MAXIMUM_MAX_PARTICLES)) && + (emission.rate == glm::clamp(emission.rate, MINIMUM_EMIT_RATE, MAXIMUM_EMIT_RATE)) && + (emission.speed.target == glm::clamp(emission.speed.target, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && + (emission.speed.spread == glm::clamp(emission.speed.spread, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) && + (emission.acceleration.target == glm::clamp(emission.acceleration.target, vec3(MINIMUM_EMIT_ACCELERATION), vec3(MAXIMUM_EMIT_ACCELERATION))) && + (emission.acceleration.spread == glm::clamp(emission.acceleration.spread, vec3(MINIMUM_ACCELERATION_SPREAD), vec3(MAXIMUM_ACCELERATION_SPREAD)) && + (emission.dimensions == glm::clamp(emission.dimensions, vec3(MINIMUM_EMIT_DIMENSION), vec3(MAXIMUM_EMIT_DIMENSION))) && + (polar.start == glm::clamp(polar.start, MINIMUM_POLAR, MAXIMUM_POLAR)) && + (polar.finish == glm::clamp(polar.finish, MINIMUM_POLAR, MAXIMUM_POLAR)) && + (azimuth.start == glm::clamp(azimuth.start, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) && + (azimuth.finish == glm::clamp(azimuth.finish, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH))); } bool Properties::emitting() const { diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index a89d7afc06..89f1e834ea 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -177,9 +177,10 @@ namespace particle { Properties& operator =(const Properties& other) { color = other.color; alpha = other.alpha; + radiusStart = other.radiusStart; + radius = other.radius; spin = other.spin; rotateWithEntity = other.rotateWithEntity; - radius = other.radius; lifespan = other.lifespan; maxParticles = other.maxParticles; emission = other.emission; diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index f1b7c8600f..b4b888122c 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -170,6 +170,13 @@ step: 0.01 } }, + { + id: "emitRadiusStart", + name: "Emit Radius Start", + type: "SliderFloat", + max: 1, + min: 0 + }, { type: "Row" }, From 5cbddfe0c47c9c96d1c739726c72ab9b0427c7ae Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 22 Oct 2018 14:08:22 -0700 Subject: [PATCH 215/276] consider all sockets in findNodeWithAddr --- libraries/networking/src/LimitedNodeList.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 7cae5d8a71..37b914143e 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -1188,7 +1188,9 @@ void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSock SharedNodePointer LimitedNodeList::findNodeWithAddr(const HifiSockAddr& addr) { QReadLocker locker(&_nodeMutex); auto it = std::find_if(std::begin(_nodeHash), std::end(_nodeHash), [&addr](const UUIDNodePair& pair) { - return pair.second->getActiveSocket() ? (*pair.second->getActiveSocket() == addr) : false; + return pair.second->getPublicSocket() == addr + || pair.second->getLocalSocket() == addr + || pair.second->getSymmetricSocket() == addr; }); return (it != std::end(_nodeHash)) ? it->second : SharedNodePointer(); } @@ -1197,8 +1199,8 @@ bool LimitedNodeList::sockAddrBelongsToNode(const HifiSockAddr& sockAddr) { QReadLocker locker(&_nodeMutex); auto it = std::find_if(std::begin(_nodeHash), std::end(_nodeHash), [&sockAddr](const UUIDNodePair& pair) { return pair.second->getPublicSocket() == sockAddr - || pair.second->getLocalSocket() == sockAddr - || pair.second->getSymmetricSocket() == sockAddr; + || pair.second->getLocalSocket() == sockAddr + || pair.second->getSymmetricSocket() == sockAddr; }); return it != std::end(_nodeHash); } From 2df9d6c5e9065fbe9b83eb975dccec98a6273f2c Mon Sep 17 00:00:00 2001 From: amantley Date: Tue, 23 Oct 2018 13:37:56 -0700 Subject: [PATCH 216/276] addressed missing changes for luis --- interface/src/avatar/MyAvatar.cpp | 32 +++++++++--------- interface/src/ui/PreferencesDialog.cpp | 46 ++++++++++++++------------ 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 78cfed9b30..71c3ba4fc6 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -530,6 +530,7 @@ void MyAvatar::update(float deltaTime) { const float PERCENTAGE_WEIGHT_HEAD_VS_SHOULDERS_AZIMUTH = 0.0f; // 100 percent shoulders const float COSINE_THIRTY_DEGREES = 0.866f; const float SQUATTY_TIMEOUT = 30.0f; // 30 seconds + const float HEIGHT_FILTER_COEFFICIENT = 0.01f; float tau = deltaTime / HMD_FACING_TIMESCALE; setHipToHandController(computeHandAzimuth()); @@ -559,7 +560,7 @@ void MyAvatar::update(float deltaTime) { controller::Pose newHeightReading = getControllerPoseInSensorFrame(controller::Action::HEAD); if (newHeightReading.isValid()) { int newHeightReadingInCentimeters = glm::floor(newHeightReading.getTranslation().y * CENTIMETERS_PER_METER); - _averageUserHeightSensorSpace = lerp(_averageUserHeightSensorSpace, newHeightReading.getTranslation().y, 0.01f); + _averageUserHeightSensorSpace = lerp(_averageUserHeightSensorSpace, newHeightReading.getTranslation().y, HEIGHT_FILTER_COEFFICIENT); _recentModeReadings.insert(newHeightReadingInCentimeters); setCurrentStandingHeight(computeStandingHeightMode(newHeightReading)); setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); @@ -4232,21 +4233,22 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co if (myAvatar.getSitStandStateChange()) { returnValue = true; - } - if (myAvatar.getIsInSittingState()) { - if (myAvatar.getIsSitStandStateLocked()) { - returnValue = (offset.y > CYLINDER_TOP); - } - if (offset.y < SITTING_BOTTOM) { - // we recenter more easily when in sitting state. - returnValue = true; - } } else { - // in the standing state - returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); - // finally check for squats in standing - if (_squatDetected) { - returnValue = true; + if (myAvatar.getIsInSittingState()) { + if (myAvatar.getIsSitStandStateLocked()) { + returnValue = (offset.y > CYLINDER_TOP); + } + if (offset.y < SITTING_BOTTOM) { + // we recenter more easily when in sitting state. + returnValue = true; + } + } else { + // in the standing state + returnValue = (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM); + // finally check for squats in standing + if (_squatDetected) { + returnValue = true; + } } } return returnValue; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 2b8f991174..34d80f50cf 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -260,27 +260,31 @@ void setupPreferences() { preferences->addPreference(preference); } { - auto getter = [myAvatar]()->int { switch (myAvatar->getUserRecenterModel()) { - case MyAvatar::SitStandModelType::Auto: - default: - return 0; - case MyAvatar::SitStandModelType::ForceSit: - return 1; - case MyAvatar::SitStandModelType::DisableHMDLean: - return 2; - }}; - auto setter = [myAvatar](int value) { switch (value) { - case 0: - default: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); - break; - case 1: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); - break; - case 2: - myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); - break; - }}; + auto getter = [myAvatar]()->int { + switch (myAvatar->getUserRecenterModel()) { + case MyAvatar::SitStandModelType::Auto: + default: + return 0; + case MyAvatar::SitStandModelType::ForceSit: + return 1; + case MyAvatar::SitStandModelType::DisableHMDLean: + return 2; + } + }; + auto setter = [myAvatar](int value) { + switch (value) { + case 0: + default: + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::Auto); + break; + case 1: + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::ForceSit); + break; + case 2: + myAvatar->setUserRecenterModel(MyAvatar::SitStandModelType::DisableHMDLean); + break; + } + }; auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Auto / Force Sit / Disable Recenter", getter, setter); QStringList items; items << "Auto - turns on avatar leaning when standing in real world" << "Seated - disables all avatar leaning while sitting in real world" << "Disabled - allows avatar sitting on the floor [Experimental]"; From 4c3f5b3bbcd0326a68cdff02b07593fa9242aa88 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 11 Oct 2018 18:43:56 +0200 Subject: [PATCH 217/276] Entity List Context Menu --- scripts/system/html/css/edit-style.css | 39 +++++ scripts/system/html/entityList.html | 1 + scripts/system/html/js/entityList.js | 94 +++++++++++- .../system/html/js/entityListContextMenu.js | 143 ++++++++++++++++++ scripts/system/libraries/entityList.js | 14 +- 5 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 scripts/system/html/js/entityListContextMenu.js diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 8e7b3f1ad5..e7e577a13b 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1801,3 +1801,42 @@ input[type=button]#export { body#entity-list-body { padding-bottom: 0; } + +.context-menu { + display: none; + position: fixed; + color: #000000; + background-color: #afafaf; + padding: 5px 0 5px 0; + cursor: default; +} +.context-menu li { + list-style-type: none; + padding: 0 5px 0 5px; + margin: 0; + white-space: nowrap; +} +.context-menu li:hover { + background-color: #e3e3e3; +} +.context-menu li.separator { + border-top: 1px solid #000000; + margin: 5px 5px; +} +.context-menu li.disabled { + color: #333333; +} +.context-menu li.separator:hover, .context-menu li.disabled:hover { + background-color: #afafaf; +} + +input.rename-entity { + height: 100%; + width: 100%; + border: none; + font-family: FiraSans-SemiBold; + font-size: 15px; + /* need this to show the text cursor when the input field is empty */ + padding-left: 2px; +} + diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index c62c785c99..ae994b56a4 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -16,6 +16,7 @@ + diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 0ced016d26..67187e6a3e 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -70,6 +70,8 @@ var selectedEntities = []; var entityList = null; // The ListView +var entityListContextMenu = new EntityListContextMenu(); + var currentSortColumn = 'type'; var currentSortOrder = ASCENDING_SORT; var isFilterInView = false; @@ -184,7 +186,83 @@ function loaded() { entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT); - + + entityListContextMenu.initialize(); + + + function startRenamingEntity(entityID) { + if (!entitiesByID[entityID] || !entitiesByID[entityID].elRow) { + return; + } + + let elCell = entitiesByID[entityID].elRow.childNodes[COLUMN_INDEX.NAME]; + let elRenameInput = document.createElement("input"); + elRenameInput.setAttribute('class', 'rename-entity'); + elRenameInput.value = entitiesByID[entityID].name; + elRenameInput.onclick = function(event) { + event.stopPropagation(); + }; + elRenameInput.onkeyup = function(keyEvent) { + if (keyEvent.key === "Enter") { + elRenameInput.blur(); + } + }; + + elRenameInput.onblur = function(event) { + let value = elRenameInput.value; + EventBridge.emitWebEvent(JSON.stringify({ + type: 'rename', + entityID: entityID, + name: value + })); + entitiesByID[entityID].name = value; + elCell.innerText = value; + }; + + elCell.innerHTML = ""; + elCell.appendChild(elRenameInput); + + elRenameInput.select(); + } + + entityListContextMenu.setCallback(function(optionName, selectedEntityID) { + switch (optionName) { + case "Copy": + EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' })); + break; + case "Paste": + EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' })); + break; + case "Rename": + startRenamingEntity(selectedEntityID); + break; + case "Duplicate": + EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' })); + break; + case "Delete": + EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); + break; + } + }); + + function onRowContextMenu(clickEvent) { + let entityID = this.dataset.entityID; + + if (!selectedEntities.includes(entityID)) { + let selection = [entityID]; + updateSelectedEntities(selection); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "selectionUpdate", + focus: false, + entityIds: selection, + })); + + refreshFooter(); + } + entityListContextMenu.open(clickEvent, entityID); + } + function onRowClicked(clickEvent) { let entityID = this.dataset.entityID; let selection = [entityID]; @@ -221,9 +299,9 @@ function loaded() { } } } else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) { - // if reselecting the same entity then deselect it + // if reselecting the same entity then start renaming it if (selectedEntities[0] === entityID) { - selection = []; + startRenamingEntity(entityID); } } @@ -502,6 +580,7 @@ function loaded() { } row.appendChild(column); } + row.oncontextmenu = onRowContextMenu; row.onclick = onRowClicked; row.ondblclick = onRowDoubleClicked; return row; @@ -672,8 +751,15 @@ function loaded() { augmentSpinButtons(); - // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked document.addEventListener("contextmenu", function (event) { + entityListContextMenu.close.call(entityListContextMenu); + + // Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked event.preventDefault(); }, false); + + // close context menu when switching focus to another window + $(window).blur(function(){ + entityListContextMenu.close.call(entityListContextMenu); + }); } diff --git a/scripts/system/html/js/entityListContextMenu.js b/scripts/system/html/js/entityListContextMenu.js new file mode 100644 index 0000000000..59ae2f1f73 --- /dev/null +++ b/scripts/system/html/js/entityListContextMenu.js @@ -0,0 +1,143 @@ +// +// entityListContextMenu.js +// +// exampleContextMenus.js was originally created by David Rowe on 22 Aug 2018. +// Modified to entityListContextMenu.js by Thijs Wenker on 10 Oct 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 +// + +/* eslint-env browser */ +const CONTEXT_MENU_CLASS = "context-menu"; + +/** + * ContextMenu class for EntityList + * @constructor + */ +function EntityListContextMenu() { + this._elContextMenu = null; + this._callback = null; +} + +EntityListContextMenu.prototype = { + + /** + * @private + */ + _elContextMenu: null, + + /** + * @private + */ + _callback: null, + + /** + * @private + */ + _selectedEntityID: null, + + /** + * Close the context menu + */ + close: function() { + if (this.isContextMenuOpen()) { + this._elContextMenu.style.display = "none"; + } + }, + + isContextMenuOpen: function() { + return this._elContextMenu.style.display === "block"; + }, + + /** + * Open the context menu + * @param clickEvent + * @param selectedEntityID + */ + open: function(clickEvent, selectedEntityID) { + this._selectedEntityID = selectedEntityID; + // If the right-clicked item has a context menu open it. + this._elContextMenu.style.display = "block"; + this._elContextMenu.style.left + = Math.min(clickEvent.pageX, document.body.offsetWidth - this._elContextMenu.offsetWidth).toString() + "px"; + this._elContextMenu.style.top + = Math.min(clickEvent.pageY, document.body.clientHeight - this._elContextMenu.offsetHeight).toString() + "px"; + clickEvent.stopPropagation(); + }, + + /** + * Set the event callback + * @param callback + */ + setCallback: function(callback) { + this._callback = callback; + }, + + /** + * Add a labeled item to the context menu + * @param itemLabel + * @param isEnabled + * @private + */ + _addListItem: function(itemLabel, isEnabled) { + let elListItem = document.createElement("li"); + elListItem.innerText = itemLabel; + + if (isEnabled === undefined || isEnabled) { + elListItem.addEventListener("click", function () { + if (this._callback) { + this._callback.call(this, itemLabel, this._selectedEntityID); + } + }.bind(this), false); + } else { + elListItem.setAttribute('class', 'disabled'); + } + this._elContextMenu.appendChild(elListItem); + }, + + /** + * Add a separator item to the context menu + * @private + */ + _addListSeparator: function() { + let elListItem = document.createElement("li"); + elListItem.setAttribute('class', 'separator'); + this._elContextMenu.appendChild(elListItem); + }, + + /** + * Initialize the context menu. + */ + initialize: function() { + this._elContextMenu = document.createElement("ul"); + this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS); + document.body.appendChild(this._elContextMenu); + + // TODO: enable Copy, Paste and Duplicate items once implemented + this._addListItem("Copy", false); + this._addListItem("Paste", false); + this._addListSeparator(); + this._addListItem("Rename"); + this._addListItem("Duplicate", false); + this._addListItem("Delete"); + + // Ignore clicks on context menu background or separator. + this._elContextMenu.addEventListener("click", function(event) { + // Sink clicks on context menu background or separator but let context menu item clicks through. + if (event.target.classList.contains(CONTEXT_MENU_CLASS)) { + event.stopPropagation(); + } + }); + + // Provide means to close context menu without clicking menu item. + document.body.addEventListener("click", this.close.bind(this)); + document.body.addEventListener("keydown", function(event) { + // Close context menu with Esc key. + if (this.isContextMenuOpen() && event.key === "Escape") { + this.close(); + } + }.bind(this)); + } +}; diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 30e952723f..d3d1d76725 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -15,7 +15,7 @@ var PROFILING_ENABLED = false; var profileIndent = ''; const PROFILE_NOOP = function(_name, fn, args) { fn.apply(this, args); -} ; +}; PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) { console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin"); var previousIndent = profileIndent; @@ -245,7 +245,7 @@ EntityListTool = function(shouldUseEditTabletApp) { Window.saveAsync("Select Where to Save", "", "*.json"); } } else if (data.type === "pal") { - var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates. + var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates. selectionManager.selections.forEach(function (id) { var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy; if (lastEditedBy) { @@ -271,6 +271,16 @@ EntityListTool = function(shouldUseEditTabletApp) { filterInView = data.filterInView === true; } else if (data.type === "radius") { searchRadius = data.radius; + } else if (data.type === "copy") { + Window.alert("Copy is not yet implemented."); + } else if (data.type === "paste") { + Window.alert("Paste is not yet implemented."); + } else if (data.type === "duplicate") { + Window.alert("Duplicate is not yet implemented."); + } else if (data.type === "rename") { + Entities.editEntity(data.entityID, {name: data.name}); + // make sure that the name also gets updated in the properties window + SelectionManager._update(); } }; From e5ef589a3cc24d0f18633ce7302ea7127209acf3 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 12 Oct 2018 21:15:25 +0200 Subject: [PATCH 218/276] style --- scripts/system/html/css/edit-style.css | 5 ++- scripts/system/html/js/entityList.js | 33 +++++++++----- .../system/html/js/entityListContextMenu.js | 45 ++++++++++++++----- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index e7e577a13b..956ae529ab 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1812,7 +1812,7 @@ body#entity-list-body { } .context-menu li { list-style-type: none; - padding: 0 5px 0 5px; + padding: 4px 18px 4px 18px; margin: 0; white-space: nowrap; } @@ -1820,8 +1820,9 @@ body#entity-list-body { background-color: #e3e3e3; } .context-menu li.separator { - border-top: 1px solid #000000; + border-top: 1px solid #333333; margin: 5px 5px; + padding: 0 0; } .context-menu li.disabled { color: #333333; diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 67187e6a3e..749f750ecb 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -70,7 +70,10 @@ var selectedEntities = []; var entityList = null; // The ListView -var entityListContextMenu = new EntityListContextMenu(); +/** + * @type EntityListContextMenu + */ +var entityListContextMenu = null; var currentSortColumn = 'type'; var currentSortOrder = ASCENDING_SORT; @@ -187,21 +190,24 @@ function loaded() { entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow, createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT); - entityListContextMenu.initialize(); + entityListContextMenu = new EntityListContextMenu(); function startRenamingEntity(entityID) { - if (!entitiesByID[entityID] || !entitiesByID[entityID].elRow) { + let entity = entitiesByID[entityID]; + if (!entity || entity.locked || !entity.elRow) { return; } - let elCell = entitiesByID[entityID].elRow.childNodes[COLUMN_INDEX.NAME]; + let elCell = entity.elRow.childNodes[COLUMN_INDEX.NAME]; let elRenameInput = document.createElement("input"); elRenameInput.setAttribute('class', 'rename-entity'); - elRenameInput.value = entitiesByID[entityID].name; - elRenameInput.onclick = function(event) { + elRenameInput.value = entity.name; + let ignoreClicks = function(event) { event.stopPropagation(); }; + elRenameInput.onclick = ignoreClicks; + elRenameInput.ondblclick = ignoreClicks; elRenameInput.onkeyup = function(keyEvent) { if (keyEvent.key === "Enter") { elRenameInput.blur(); @@ -215,7 +221,7 @@ function loaded() { entityID: entityID, name: value })); - entitiesByID[entityID].name = value; + entity.name = value; elCell.innerText = value; }; @@ -260,13 +266,20 @@ function loaded() { refreshFooter(); } - entityListContextMenu.open(clickEvent, entityID); + + let enabledContextMenuItems = []; + if (entitiesByID[entityID] && !entitiesByID[entityID].locked) { + enabledContextMenuItems.push('Rename'); + enabledContextMenuItems.push('Delete'); + } + + entityListContextMenu.open(clickEvent, entityID, enabledContextMenuItems); } function onRowClicked(clickEvent) { let entityID = this.dataset.entityID; let selection = [entityID]; - + if (clickEvent.ctrlKey) { let selectedIndex = selectedEntities.indexOf(entityID); if (selectedIndex >= 0) { @@ -759,7 +772,7 @@ function loaded() { }, false); // close context menu when switching focus to another window - $(window).blur(function(){ + $(window).blur(function() { entityListContextMenu.close.call(entityListContextMenu); }); } diff --git a/scripts/system/html/js/entityListContextMenu.js b/scripts/system/html/js/entityListContextMenu.js index 59ae2f1f73..62fe951ebe 100644 --- a/scripts/system/html/js/entityListContextMenu.js +++ b/scripts/system/html/js/entityListContextMenu.js @@ -19,6 +19,8 @@ const CONTEXT_MENU_CLASS = "context-menu"; function EntityListContextMenu() { this._elContextMenu = null; this._callback = null; + this._listItems = []; + this._initialize(); } EntityListContextMenu.prototype = { @@ -38,6 +40,11 @@ EntityListContextMenu.prototype = { */ _selectedEntityID: null, + /** + * @private + */ + _listItems: null, + /** * Close the context menu */ @@ -55,10 +62,17 @@ EntityListContextMenu.prototype = { * Open the context menu * @param clickEvent * @param selectedEntityID + * @param enabledOptions */ - open: function(clickEvent, selectedEntityID) { + open: function(clickEvent, selectedEntityID, enabledOptions) { this._selectedEntityID = selectedEntityID; - // If the right-clicked item has a context menu open it. + + this._listItems.forEach(function(listItem) { + let enabled = enabledOptions.includes(listItem.label); + listItem.enabled = enabled; + listItem.element.setAttribute('class', enabled ? '' : 'disabled'); + }); + this._elContextMenu.style.display = "block"; this._elContextMenu.style.left = Math.min(clickEvent.pageX, document.body.offsetWidth - this._elContextMenu.offsetWidth).toString() + "px"; @@ -85,15 +99,21 @@ EntityListContextMenu.prototype = { let elListItem = document.createElement("li"); elListItem.innerText = itemLabel; - if (isEnabled === undefined || isEnabled) { - elListItem.addEventListener("click", function () { - if (this._callback) { - this._callback.call(this, itemLabel, this._selectedEntityID); - } - }.bind(this), false); - } else { - elListItem.setAttribute('class', 'disabled'); - } + let listItem = { + label: itemLabel, + element: elListItem, + enabled: false + }; + + elListItem.addEventListener("click", function () { + if (listItem.enabled && this._callback) { + this._callback.call(this, itemLabel, this._selectedEntityID); + } + }.bind(this), false); + + elListItem.setAttribute('class', 'disabled'); + + this._listItems.push(listItem); this._elContextMenu.appendChild(elListItem); }, @@ -109,8 +129,9 @@ EntityListContextMenu.prototype = { /** * Initialize the context menu. + * @private */ - initialize: function() { + _initialize: function() { this._elContextMenu = document.createElement("ul"); this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS); document.body.appendChild(this._elContextMenu); From 4069ca2e0c39a51d1b3eb911d62a19439c3c0b9d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 20 Oct 2018 02:43:41 +0200 Subject: [PATCH 219/276] CR feedback --- scripts/system/html/js/entityList.js | 11 ++++---- .../system/html/js/entityListContextMenu.js | 27 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 749f750ecb..663acb758c 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -231,8 +231,11 @@ function loaded() { elRenameInput.select(); } - entityListContextMenu.setCallback(function(optionName, selectedEntityID) { + entityListContextMenu.setOnSelectedCallback(function(optionName, selectedEntityID) { switch (optionName) { + case "Cut": + EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' })); + break; case "Copy": EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' })); break; @@ -263,8 +266,6 @@ function loaded() { focus: false, entityIds: selection, })); - - refreshFooter(); } let enabledContextMenuItems = []; @@ -765,7 +766,7 @@ function loaded() { augmentSpinButtons(); document.addEventListener("contextmenu", function (event) { - entityListContextMenu.close.call(entityListContextMenu); + entityListContextMenu.close(); // Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked event.preventDefault(); @@ -773,6 +774,6 @@ function loaded() { // close context menu when switching focus to another window $(window).blur(function() { - entityListContextMenu.close.call(entityListContextMenu); + entityListContextMenu.close(); }); } diff --git a/scripts/system/html/js/entityListContextMenu.js b/scripts/system/html/js/entityListContextMenu.js index 62fe951ebe..d71719f252 100644 --- a/scripts/system/html/js/entityListContextMenu.js +++ b/scripts/system/html/js/entityListContextMenu.js @@ -18,7 +18,7 @@ const CONTEXT_MENU_CLASS = "context-menu"; */ function EntityListContextMenu() { this._elContextMenu = null; - this._callback = null; + this._onSelectedCallback = null; this._listItems = []; this._initialize(); } @@ -33,7 +33,7 @@ EntityListContextMenu.prototype = { /** * @private */ - _callback: null, + _onSelectedCallback: null, /** * @private @@ -82,20 +82,19 @@ EntityListContextMenu.prototype = { }, /** - * Set the event callback - * @param callback + * Set the callback for when a menu item is selected + * @param onSelectedCallback */ - setCallback: function(callback) { - this._callback = callback; + setOnSelectedCallback: function(onSelectedCallback) { + this._onSelectedCallback = onSelectedCallback; }, /** * Add a labeled item to the context menu * @param itemLabel - * @param isEnabled * @private */ - _addListItem: function(itemLabel, isEnabled) { + _addListItem: function(itemLabel) { let elListItem = document.createElement("li"); elListItem.innerText = itemLabel; @@ -106,8 +105,8 @@ EntityListContextMenu.prototype = { }; elListItem.addEventListener("click", function () { - if (listItem.enabled && this._callback) { - this._callback.call(this, itemLabel, this._selectedEntityID); + if (listItem.enabled && this._onSelectedCallback) { + this._onSelectedCallback.call(this, itemLabel, this._selectedEntityID); } }.bind(this), false); @@ -136,12 +135,12 @@ EntityListContextMenu.prototype = { this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS); document.body.appendChild(this._elContextMenu); - // TODO: enable Copy, Paste and Duplicate items once implemented - this._addListItem("Copy", false); - this._addListItem("Paste", false); + this._addListItem("Cut"); + this._addListItem("Copy"); + this._addListItem("Paste"); this._addListSeparator(); this._addListItem("Rename"); - this._addListItem("Duplicate", false); + this._addListItem("Duplicate"); this._addListItem("Delete"); // Ignore clicks on context menu background or separator. From 3fa4ee0e1ddede8e7905200a179645c750f8cfc8 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Sat, 20 Oct 2018 03:25:52 +0200 Subject: [PATCH 220/276] enable duplicate copy cut paste functionality of context menu --- scripts/system/html/js/entityList.js | 3 ++- scripts/system/libraries/entityList.js | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 663acb758c..d01e68cff2 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -268,8 +268,9 @@ function loaded() { })); } - let enabledContextMenuItems = []; + let enabledContextMenuItems = ['Copy', 'Paste', 'Duplicate']; if (entitiesByID[entityID] && !entitiesByID[entityID].locked) { + enabledContextMenuItems.push('Cut'); enabledContextMenuItems.push('Rename'); enabledContextMenuItems.push('Delete'); } diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index d3d1d76725..3d73f2f115 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -271,12 +271,15 @@ EntityListTool = function(shouldUseEditTabletApp) { filterInView = data.filterInView === true; } else if (data.type === "radius") { searchRadius = data.radius; + } else if (data.type === "cut") { + cutSelectedEntities(); } else if (data.type === "copy") { - Window.alert("Copy is not yet implemented."); + copySelectedEntities(); } else if (data.type === "paste") { - Window.alert("Paste is not yet implemented."); + pasteEntities(); } else if (data.type === "duplicate") { - Window.alert("Duplicate is not yet implemented."); + SelectionManager.duplicateSelection(); + that.sendUpdate(); } else if (data.type === "rename") { Entities.editEntity(data.entityID, {name: data.name}); // make sure that the name also gets updated in the properties window From 4e9f9b3edf9960af9ea82eb784c7cecd631c94e2 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Oct 2018 22:39:40 +0200 Subject: [PATCH 221/276] comply with copy/paste update --- scripts/system/libraries/entityList.js | 6 +++--- scripts/system/libraries/entitySelectionTool.js | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 3d73f2f115..da31ef8463 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -272,11 +272,11 @@ EntityListTool = function(shouldUseEditTabletApp) { } else if (data.type === "radius") { searchRadius = data.radius; } else if (data.type === "cut") { - cutSelectedEntities(); + SelectionManager.cutSelectedEntities(); } else if (data.type === "copy") { - copySelectedEntities(); + SelectionManager.copySelectedEntities(); } else if (data.type === "paste") { - pasteEntities(); + SelectionManager.pasteEntities(); } else if (data.type === "duplicate") { SelectionManager.duplicateSelection(); that.sendUpdate(); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 843d3e986f..a117df6fc7 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -353,12 +353,12 @@ SelectionManager = (function() { } return createdEntityIDs; - } + }; that.cutSelectedEntities = function() { - copySelectedEntities(); + that.copySelectedEntities(); deleteSelectedEntities(); - } + }; that.copySelectedEntities = function() { var entityProperties = Entities.getMultipleEntityProperties(that.selections); @@ -434,7 +434,7 @@ SelectionManager = (function() { z: brn.z + entityClipboard.dimensions.z / 2 }; } - } + }; that.pasteEntities = function() { var dimensions = entityClipboard.dimensions; @@ -442,7 +442,7 @@ SelectionManager = (function() { var pastePosition = getPositionToCreateEntity(maxDimension); var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position); - var copiedProperties = [] + var copiedProperties = []; var ids = []; entityClipboard.entities.forEach(function(originalProperties) { var properties = deepCopy(originalProperties); @@ -475,7 +475,7 @@ SelectionManager = (function() { redo(copiedProperties); undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); - } + }; that._update = function(selectionUpdated) { var properties = null; From 68ab5475feb10472c8d1e8ab612d492e72b077c2 Mon Sep 17 00:00:00 2001 From: Alexia Mandeville Date: Tue, 23 Oct 2018 14:13:09 -0700 Subject: [PATCH 222/276] Remove duplicate display crash options --- interface/src/Menu.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 5e1c9412e8..c4835f9741 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -742,9 +742,6 @@ Menu::Menu() { // Developer > Crash >>> MenuWrapper* crashMenu = developerMenu->addMenu("Crash"); - - // Developer > Crash > Display Crash Options - addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true); // Developer > Crash > Display Crash Options addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true); From 10ee5ac5b422144a8ac443233f1dcb3ff75f6b02 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Tue, 23 Oct 2018 14:46:13 -0700 Subject: [PATCH 223/276] URL protocol returns the protocol string with the ':' attached (how did this work before?!) --- server-console/src/modules/hf-notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index 3ee2bd13a4..8a812625b4 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -157,7 +157,7 @@ function HifiNotifications(config, menuNotificationCallback) { var _menuNotificationCallback = menuNotificationCallback; notifier.on('click', function (notifierObject, options) { const optUrl = url.parse(options.url); - if ((optUrl.protocol === "hifi") || (optUrl.protocol === "hifiapp")) { + if ((optUrl.protocol === "hifi:") || (optUrl.protocol === "hifiapp:")) { StartInterface(options.url); _menuNotificationCallback(options.notificationType, false); } else { From 2b9dcd0875324a53b67d3cb1b1c2224f6938c7ee Mon Sep 17 00:00:00 2001 From: David Back Date: Tue, 23 Oct 2018 18:38:05 -0700 Subject: [PATCH 224/276] fix particle properties and keylight direction, remove columns, fix orderings, fix sliders --- scripts/system/edit.js | 31 +++- scripts/system/html/css/edit-style.css | 93 +++++----- scripts/system/html/js/entityProperties.js | 197 ++++++++++++--------- 3 files changed, 186 insertions(+), 135 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 53df4176fe..739229e7a7 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2182,10 +2182,14 @@ var PropertiesTool = function (opts) { if (entity.properties.rotation !== undefined) { entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation); } + if (entity.properties.emitOrientation !== undefined) { + entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); + } if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { - entity.properties.keyLight.direction = Vec3.multiply(RADIANS_TO_DEGREES, - Vec3.toPolar(entity.properties.keyLight.direction)); + print("DBACK TEST entity.properties.keyLight.direction pre " + entity.properties.keyLight.direction.x + " " + entity.properties.keyLight.direction.y + " " + entity.properties.keyLight.direction.z); + entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); entity.properties.keyLight.direction.z = 0.0; + print("DBACK TEST entity.properties.keyLight.direction post " + entity.properties.keyLight.direction.x + " " + entity.properties.keyLight.direction.y + " " + entity.properties.keyLight.direction.z); } selections.push(entity); } @@ -2220,18 +2224,27 @@ var PropertiesTool = function (opts) { data.properties.angularVelocity = Vec3.ZERO; } if (data.properties.rotation !== undefined) { - var rotation = data.properties.rotation; - data.properties.rotation = Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z); + data.properties.rotation = Quat.fromVec3Degrees(data.properties.rotation); + } + if (data.properties.emitOrientation !== undefined) { + data.properties.emitOrientation = Quat.fromVec3Degrees(data.properties.emitOrientation); } if (data.properties.keyLight !== undefined && data.properties.keyLight.direction !== undefined) { - data.properties.keyLight.direction = Vec3.fromPolar( - data.properties.keyLight.direction.x * DEGREES_TO_RADIANS, - data.properties.keyLight.direction.y * DEGREES_TO_RADIANS - ); + var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); + print("DBACK TEST data.properties.keyLight.direction pre pre " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z + " currentKeyLightDirection " + currentKeyLightDirection.x + " " + currentKeyLightDirection.y + " " + currentKeyLightDirection.z); + if (data.properties.keyLight.direction.x === undefined) { + data.properties.keyLight.direction.x = currentKeyLightDirection.x; + } + if (data.properties.keyLight.direction.y === undefined) { + data.properties.keyLight.direction.y = currentKeyLightDirection.y; + } + print("DBACK TEST data.properties.keyLight.direction pre " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z); + data.properties.keyLight.direction = Vec3.fromPolar(data.properties.keyLight.direction.x, data.properties.keyLight.direction.y); + print("DBACK TEST data.properties.keyLight.direction post " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z); } Entities.editEntity(selectionManager.selections[0], data.properties); if (data.properties.name !== undefined || data.properties.modelURL !== undefined || data.properties.materialURL !== undefined || - data.properties.visible !== undefined || data.properties.locked !== undefined) { + data.properties.visible !== undefined || data.properties.locked !== undefined) { entityListTool.sendUpdate(); } } diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index c4ab00e689..339b2498f9 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -470,6 +470,11 @@ input[type=checkbox]:checked + label:hover { border: 1.5pt solid black; } +#properties-list { + display: flex; + flex-direction: column; +} + #properties-list fieldset { position: relative; /* 0.1px on the top is to prevent margin collapsing between this and it's first child */ @@ -551,6 +556,16 @@ div.section-header:first-child { margin-bottom: -21px; } +#properties-list .sub-section-header { + border-top: none; + box-shadow: none; + margin-top: 8px; +} + +.sub-section-header + .property { + margin-top: 0; +} + hr { border: none; padding-top: 2px; @@ -634,7 +649,7 @@ hr { height: 1.8rem; } -.text label, .url label, .number label, .textarea label, .xy label, .wh label, .rgb label, .xyz label,.pyr label, .dropdown label, .gen label { +.text label, .url label, .number label, .textarea label, .xy label, .wh label, .rgb label, .xyz label, .pyr label, .dropdown label, .gen label { float: left; margin-left: 1px; margin-bottom: 3px; @@ -648,6 +663,10 @@ hr { margin-top: -2px; } +.xy > div, .wh > div, .xyz > div, .pyr > div, .gen > div { + clear: both; +} + .number > input { clear: both; float: left; @@ -656,9 +675,6 @@ hr { clear: both; float: left; } -.xy > div, .wh > div, .xyz > div, .pyr > div, .gen > div { - clear: both; -} .dropdown { position: relative; @@ -1018,6 +1034,10 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { } +body#entity-list-body { + padding-bottom: 0; +} + #entity-list-header { margin-bottom: 36px; } @@ -1054,16 +1074,6 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { position: relative; /* New positioning context. */ } -#footer-text { - float: right; - padding-top: 12px; - padding-right: 22px; -} - -#entity-list-footer { - padding-top: 9px; -} - #search-area { padding-right: 168px; padding-bottom: 24px; @@ -1091,6 +1101,32 @@ textarea:enabled[scrolling="true"]::-webkit-resizer { width: 120px; } +#entity-list-footer { + padding-top: 9px; +} + +#footer-text { + float: right; + padding-top: 12px; + padding-right: 22px; +} + +input[type=button]#export { + height: 38px; + width: 180px; +} + +#no-entities { + display: none; + position: absolute; + top: 80px; + padding: 12px; + font-family: FiraSans-SemiBold; + font-size: 15px; + font-style: italic; + color: #afafaf; +} + #entity-table-scroll { /* Height is set by JavaScript. */ width: 100%; @@ -1418,33 +1454,6 @@ input#property-scale-button-reset { right: -20px; } -#properties-list #collision-info > fieldset:first-of-type { - border-top: none !important; - box-shadow: none; +#div-property-collisionSoundURL[style*="display: none"] + .property { margin-top: 0; } - -#properties-list { - display: flex; - flex-direction: column; -} - -#no-entities { - display: none; - position: absolute; - top: 80px; - padding: 12px; - font-family: FiraSans-SemiBold; - font-size: 15px; - font-style: italic; - color: #afafaf; -} - -input[type=button]#export { - height: 38px; - width: 180px; -} - -body#entity-list-body { - padding-bottom: 0; -} diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index e5bb80ef2c..a3f986ad53 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -181,6 +181,7 @@ const GROUPS = [ { label: "Light Horizontal Angle", type: "number", + multiplier: DEGREES_TO_RADIANS, decimals: 2, unit: "deg", propertyID: "keyLight.direction.x", @@ -189,6 +190,7 @@ const GROUPS = [ { label: "Light Vertical Angle", type: "number", + multiplier: DEGREES_TO_RADIANS, decimals: 2, unit: "deg", propertyID: "keyLight.direction.y", @@ -301,8 +303,8 @@ const GROUPS = [ { label: "Background Blend", type: "slider", - min: 0.0, - max: 1.0, + min: 0, + max: 1, step: 0.01, decimals: 2, propertyID: "haze.hazeBackgroundBlend", @@ -610,6 +612,7 @@ const GROUPS = [ min: 0.01, max: 10, step: 0.01, + decimals: 2, propertyID: "lifespan", }, { @@ -646,6 +649,7 @@ const GROUPS = [ min: 0, max: 5, step: 0.01, + decimals: 2, propertyID: "emitSpeed", }, { @@ -654,6 +658,7 @@ const GROUPS = [ min: 0, max: 5, step: 0.01, + decimals: 2, propertyID: "speedSpread", }, { @@ -662,6 +667,7 @@ const GROUPS = [ vec3Type: "xyz", min: 0, step: 0.01, + round: 100, subLabels: [ "x", "y", "z" ], propertyID: "emitDimensions", }, @@ -669,8 +675,8 @@ const GROUPS = [ label: "Emit Orientation", type: "vec3", vec3Type: "pyr", - min: 0, step: 0.01, + round: 100, subLabels: [ "pitch", "yaw", "roll" ], unit: "deg", propertyID: "emitOrientation", @@ -689,30 +695,38 @@ const GROUPS = [ { label: "Size", type: "slider", + min: 0, max: 4, step: 0.01, + decimals: 2, propertyID: "particleRadius", }, { label: "Size Spread", type: "slider", + min: 0, max: 4, step: 0.01, + decimals: 2, propertyID: "radiusSpread", }, { label: "Size Start", type: "slider", + min: 0, max: 4, step: 0.01, + decimals: 2, propertyID: "radiusStart", fallbackProperty: "particleRadius", }, { label: "Size Finish", type: "slider", + min: 0, max: 4, step: 0.01, + decimals: 2, propertyID: "radiusFinish", fallbackProperty: "particleRadius", }, @@ -757,6 +771,7 @@ const GROUPS = [ min: 0, max: 1, step: 0.01, + decimals: 2, propertyID: "alpha", }, { @@ -765,6 +780,7 @@ const GROUPS = [ min: 0, max: 1, step: 0.01, + decimals: 2, propertyID: "alphaSpread", }, { @@ -773,6 +789,7 @@ const GROUPS = [ min: 0, max: 1, step: 0.01, + decimals: 2, propertyID: "alphaStart", fallbackProperty: "alpha", }, @@ -782,6 +799,7 @@ const GROUPS = [ min: 0, max: 1, step: 0.01, + decimals: 2, propertyID: "alphaFinish", fallbackProperty: "alpha", }, @@ -796,6 +814,7 @@ const GROUPS = [ type: "vec3", vec3Type: "xyz", step: 0.01, + round: 100, subLabels: [ "x", "y", "z" ], propertyID: "emitAcceleration", }, @@ -804,6 +823,7 @@ const GROUPS = [ type: "vec3", vec3Type: "xyz", step: 0.01, + round: 100, subLabels: [ "x", "y", "z" ], propertyID: "accelerationSpread", }, @@ -819,6 +839,7 @@ const GROUPS = [ min: -360, max: 360, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "particleSpin", @@ -829,6 +850,7 @@ const GROUPS = [ min: 0, max: 360, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "spinSpread", @@ -839,6 +861,7 @@ const GROUPS = [ min: -360, max: 360, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "spinStart", @@ -850,6 +873,7 @@ const GROUPS = [ min: -360, max: 360, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "spinFinish", @@ -869,9 +893,10 @@ const GROUPS = [ { label: "Horizontal Angle Start", type: "slider", - min: 0, - max: 180, + min: -180, + max: 0, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "azimuthStart", @@ -879,9 +904,10 @@ const GROUPS = [ { label: "Horizontal Angle Finish", type: "slider", - min: -180, - max: 0, + min: 0, + max: 180, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "azimuthFinish", @@ -892,6 +918,7 @@ const GROUPS = [ min: 0, max: 180, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "polarStart", @@ -902,6 +929,7 @@ const GROUPS = [ min: 0, max: 180, step: 1, + decimals: 0, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "polarFinish", @@ -935,6 +963,7 @@ const GROUPS = [ label: "Dimension", type: "vec3", vec3Type: "xyz", + min: 0, step: 0.1, decimals: 4, subLabels: [ "x", "y", "z" ], @@ -972,31 +1001,16 @@ const GROUPS = [ { id: "behavior", label: "BEHAVIOR", - twoColumn: true, properties: [ { label: "Grabbable", type: "bool", propertyID: "grab.grabbable", - column: 1, - }, - { - label: "Triggerable", - type: "bool", - propertyID: "grab.triggerable", - column: 2, }, { label: "Cloneable", type: "bool", propertyID: "cloneable", - column: 1, - }, - { - label: "Follow Controller", - type: "bool", - propertyID: "grab.grabFollowsController", - column: 2, }, { label: "Clone Lifetime", @@ -1004,30 +1018,36 @@ const GROUPS = [ unit: "s", propertyID: "cloneLifetime", showPropertyRule: { "cloneable": "true" }, - column: 1, }, { label: "Clone Limit", type: "number", propertyID: "cloneLimit", showPropertyRule: { "cloneable": "true" }, - column: 1, }, { label: "Clone Dynamic", type: "bool", propertyID: "cloneDynamic", showPropertyRule: { "cloneable": "true" }, - column: 1, }, { label: "Clone Avatar Entity", type: "bool", propertyID: "cloneAvatarEntity", showPropertyRule: { "cloneable": "true" }, - column: 1, }, - { // below properties having no column number means place them after two columns div + { + label: "Triggerable", + type: "bool", + propertyID: "grab.triggerable", + }, + { + label: "Follow Controller", + type: "bool", + propertyID: "grab.grabFollowsController", + }, + { label: "Cast shadows", type: "bool", propertyID: "canCastShadow", @@ -1068,34 +1088,18 @@ const GROUPS = [ { id: "collision", label: "COLLISION", - twoColumn: true, properties: [ { label: "Collides", type: "bool", inverse: true, propertyID: "collisionless", - column: -1, // before two columns div - }, - { - label: "Dynamic", - type: "bool", - propertyID: "dynamic", - column: -1, // before two columns div }, { label: "Collides With", type: "sub-header", propertyID: "collidesWithHeader", // not actually a property but used for naming/storing this element showPropertyRule: { "collisionless": "false" }, - column: 1, - }, - { - label: "", - type: "sub-header", - propertyID: "collidesWithHeaderHelper", // not actually a property but used for naming/storing this element - showPropertyRule: { "collisionless": "false" }, - column: 2, }, { label: "Static Entities", @@ -1104,16 +1108,6 @@ const GROUPS = [ propertyName: "static", // actual subProperty name subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, - column: 1, - }, - { - label: "Dynamic Entities", - type: "bool", - propertyID: "collidesWithDynamic", - propertyName: "dynamic", // actual subProperty name - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - column: 2, }, { label: "Kinematic Entities", @@ -1122,7 +1116,14 @@ const GROUPS = [ propertyName: "kinematic", // actual subProperty name subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, - column: 1, + }, + { + label: "Dynamic Entities", + type: "bool", + propertyID: "collidesWithDynamic", + propertyName: "dynamic", // actual subProperty name + subPropertyOf: "collidesWith", + showPropertyRule: { "collisionless": "false" }, }, { label: "My Avatar", @@ -1131,7 +1132,6 @@ const GROUPS = [ propertyName: "myAvatar", // actual subProperty name subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, - column: 2, }, { label: "Other Avatars", @@ -1140,14 +1140,17 @@ const GROUPS = [ propertyName: "otherAvatar", // actual subProperty name subPropertyOf: "collidesWith", showPropertyRule: { "collisionless": "false" }, - column: 1, }, { label: "Collision sound URL", type: "string", propertyID: "collisionSoundURL", showPropertyRule: { "collisionless": "false" }, - // having no column number means place this after two columns div + }, + { + label: "Dynamic", + type: "bool", + propertyID: "dynamic", }, ] }, @@ -1555,15 +1558,12 @@ function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { }; } -function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, decimals) { +function createEmitNumberPropertyUpdateFunction(propertyName, multiplier) { return function() { - let value = parseFloat(this.value); - if (multiplier !== undefined) { - value *= multiplier; - } - if (decimals !== undefined) { - value = value.toFixed(decimals); + if (multiplier === undefined) { + multiplier = 1; } + let value = parseFloat(this.value) * multiplier; updateProperty(propertyName, value); }; } @@ -1720,7 +1720,7 @@ function createNumberProperty(property, elProperty, elLabel) { elInput.value = defaultValue; } - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.muliplier, propertyData.decimals)); + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals)); elProperty.appendChild(elLabel); elProperty.appendChild(elInput); @@ -1760,24 +1760,31 @@ function createSliderProperty(property, elProperty, elLabel) { elSlider.setAttribute("step", propertyData.step); } - elInput.oninput = function (event) { - let value = event.target.value; - elSlider.value = value; + elInput.onchange = function (event) { + let inputValue = event.target.value; + elSlider.value = inputValue; if (propertyData.multiplier !== undefined) { - value *= propertyData.multiplier; + inputValue *= propertyData.multiplier; } - updateProperty(property.name, value); + updateProperty(property.name, inputValue); }; - elInput.onchange = elInput.oninput; elSlider.oninput = function (event) { - let value = event.target.value; - elInput.value = value; - if (propertyData.multiplier !== undefined) { - value *= propertyData.multiplier; + let sliderValue = event.target.value; + if (propertyData.step === 1) { + if (sliderValue > 0) { + elInput.value = Math.floor(sliderValue); + } else { + elInput.value = Math.ceil(sliderValue); + } + } else { + elInput.value = sliderValue; } - updateProperty(property.name, value); + if (propertyData.multiplier !== undefined) { + sliderValue *= propertyData.multiplier; + } + updateProperty(property.name, sliderValue); }; - + elDiv.appendChild(elLabel); elDiv.appendChild(elSlider); elDiv.appendChild(elInput); @@ -2952,8 +2959,15 @@ function loaded() { case 'number': case 'slider': { let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; - let decimals = propertyData.decimals !== undefined ? propertyData.decimals : 0; - property.elInput.value = (propertyValue / multiplier).toFixed(decimals); + let value = propertyValue / multiplier; + if (propertyData.round !== undefined) { + value = Math.round(value.round) / propertyData.round; + } + if (propertyData.decimals !== undefined) { + property.elInput.value = value.toFixed(propertyData.decimals); + } else { + property.elInput.value = value; + } if (property.elSlider !== undefined) { property.elSlider.value = property.elInput.value; } @@ -2962,11 +2976,26 @@ function loaded() { case 'vec3': case 'vec2': { let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; - let decimals = propertyData.decimals !== undefined ? propertyData.decimals : 0; - property.elInputX.value = (propertyValue.x / multiplier).toFixed(decimals); - property.elInputY.value = (propertyValue.y / multiplier).toFixed(decimals); - if (property.elInputZ !== undefined) { - property.elInputZ.value = (propertyValue.z / multiplier).toFixed(decimals); + let valueX = propertyValue.x / multiplier; + let valueY = propertyValue.y / multiplier; + let valueZ = propertyValue.z / multiplier; + if (propertyData.round !== undefined) { + valueX = Math.round(valueX * propertyData.round) / propertyData.round; + valueY = Math.round(valueY * propertyData.round) / propertyData.round; + valueZ = Math.round(valueZ * propertyData.round) / propertyData.round; + } + if (propertyData.decimals !== undefined) { + property.elInputX.value = valueX.toFixed(propertyData.decimals); + property.elInputY.value = valueY.toFixed(propertyData.decimals); + if (property.elInputZ !== undefined) { + property.elInputZ.value = valueZ.toFixed(propertyData.decimals); + } + } else { + property.elInputX.value = valueX; + property.elInputY.value = valueY; + if (property.elInputZ !== undefined) { + property.elInputZ.value = valueZ; + } } break; } From c7ba386be1c85ccbf266484ab81561057aae216d Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 23 Oct 2018 20:56:53 -0700 Subject: [PATCH 225/276] darn prints somehow slipping past me --- scripts/system/edit.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 739229e7a7..287df1fdca 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2186,10 +2186,8 @@ var PropertiesTool = function (opts) { entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation); } if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) { - print("DBACK TEST entity.properties.keyLight.direction pre " + entity.properties.keyLight.direction.x + " " + entity.properties.keyLight.direction.y + " " + entity.properties.keyLight.direction.z); entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction); entity.properties.keyLight.direction.z = 0.0; - print("DBACK TEST entity.properties.keyLight.direction post " + entity.properties.keyLight.direction.x + " " + entity.properties.keyLight.direction.y + " " + entity.properties.keyLight.direction.z); } selections.push(entity); } @@ -2231,16 +2229,13 @@ var PropertiesTool = function (opts) { } if (data.properties.keyLight !== undefined && data.properties.keyLight.direction !== undefined) { var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction); - print("DBACK TEST data.properties.keyLight.direction pre pre " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z + " currentKeyLightDirection " + currentKeyLightDirection.x + " " + currentKeyLightDirection.y + " " + currentKeyLightDirection.z); if (data.properties.keyLight.direction.x === undefined) { data.properties.keyLight.direction.x = currentKeyLightDirection.x; } if (data.properties.keyLight.direction.y === undefined) { data.properties.keyLight.direction.y = currentKeyLightDirection.y; } - print("DBACK TEST data.properties.keyLight.direction pre " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z); data.properties.keyLight.direction = Vec3.fromPolar(data.properties.keyLight.direction.x, data.properties.keyLight.direction.y); - print("DBACK TEST data.properties.keyLight.direction post " + data.properties.keyLight.direction.x + " " + data.properties.keyLight.direction.y + " " + data.properties.keyLight.direction.z); } Entities.editEntity(selectionManager.selections[0], data.properties); if (data.properties.name !== undefined || data.properties.modelURL !== undefined || data.properties.materialURL !== undefined || From 28c062b842e0883a332568e37cbcdb8e408fc77a Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Wed, 24 Oct 2018 11:57:59 -0300 Subject: [PATCH 226/276] Don't show stats in release builds --- interface/resources/qml/+android/Stats.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android/Stats.qml index 0dcb07e730..e9a2aa47eb 100644 --- a/interface/resources/qml/+android/Stats.qml +++ b/interface/resources/qml/+android/Stats.qml @@ -10,6 +10,7 @@ Item { property int modality: Qt.NonModal implicitHeight: row.height implicitWidth: row.width + visible: false Component.onCompleted: { stats.parentChanged.connect(fill); From 27e0c85b01d799ac4268b82fad863b21bcf57e88 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 5 Oct 2018 22:13:45 +0200 Subject: [PATCH 227/276] entity list auto scroll --- scripts/system/html/js/entityList.js | 35 +++++++++++++++---- scripts/system/html/js/listView.js | 18 ++++++++++ scripts/system/libraries/entityList.js | 7 ++-- .../system/libraries/entitySelectionTool.js | 8 ++--- 4 files changed, 55 insertions(+), 13 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 0ced016d26..0c87470f57 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -194,7 +194,7 @@ function loaded() { if (selectedIndex >= 0) { selection = []; selection = selection.concat(selectedEntities); - selection.splice(selectedIndex, 1) + selection.splice(selectedIndex, 1); } else { selection = selection.concat(selectedEntities); } @@ -453,7 +453,10 @@ function loaded() { } } - function updateSelectedEntities(selectedIDs) { + function updateSelectedEntities(selectedIDs, autoScroll) { + // force autoScroll to be a boolean + autoScroll = !!autoScroll; + let notFound = false; // reset all currently selected entities and their rows first @@ -482,6 +485,26 @@ function loaded() { } }); + if (autoScroll && selectedIDs.length > 0) { + let firstItem = Number.MAX_VALUE; + let lastItem = -1; + let itemFound = false; + visibleEntities.forEach(function(entity, index) { + if (selectedIDs.indexOf(entity.id) !== -1) { + if (firstItem > index) { + firstItem = index; + } + if (lastItem < index) { + lastItem = index; + } + itemFound = true; + } + }); + if (itemFound) { + entityList.scrollToRow(firstItem, lastItem); + } + } + refreshFooter(); return notFound; @@ -640,8 +663,8 @@ function loaded() { data = JSON.parse(data); if (data.type === "clearEntityList") { clearEntities(); - } else if (data.type === "selectionUpdate") { - let notFound = updateSelectedEntities(data.selectedIDs); + } else if (data.type === "selectionUpdate" && data.caller !== "entityList") { + let notFound = updateSelectedEntities(data.selectedIDs, true); if (notFound) { refreshEntities(); } @@ -653,13 +676,13 @@ function loaded() { clearEntities(); } else { updateEntityData(newEntities); - updateSelectedEntities(data.selectedIDs); + updateSelectedEntities(data.selectedIDs, true); } } }); } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { removeEntities(data.deletedIDs); - updateSelectedEntities(data.selectedIDs); + updateSelectedEntities(data.selectedIDs, true); } else if (data.type === "deleted" && data.ids) { removeEntities(data.ids); } diff --git a/scripts/system/html/js/listView.js b/scripts/system/html/js/listView.js index 10dc37efba..d11452e111 100644 --- a/scripts/system/html/js/listView.js +++ b/scripts/system/html/js/listView.js @@ -152,6 +152,24 @@ ListView.prototype = { this.refresh(); } }, + + scrollToRow(rowIndex, lastRowIndex) { + lastRowIndex = lastRowIndex ? lastRowIndex : rowIndex; + let boundingTop = rowIndex * this.rowHeight; + let boundingBottom = (lastRowIndex * this.rowHeight) + this.rowHeight; + if ((boundingBottom - boundingTop) > this.elTableScroll.clientHeight) { + boundingBottom = boundingTop + this.elTableScroll.clientHeight; + } + + let currentVisibleAreaTop = this.elTableScroll.scrollTop; + let currentVisibleAreaBottom = currentVisibleAreaTop + this.elTableScroll.clientHeight; + + if (boundingTop < currentVisibleAreaTop) { + this.elTableScroll.scrollTop = boundingTop; + } else if (boundingBottom > currentVisibleAreaBottom) { + this.elTableScroll.scrollTop = boundingBottom - (this.elTableScroll.clientHeight); + } + }, refresh: function() { // block refreshing before rows are initialized diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 30e952723f..04b19aa280 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -98,7 +98,7 @@ EntityListTool = function(shouldUseEditTabletApp) { that.setVisible(!visible); }; - selectionManager.addEventListener(function() { + selectionManager.addEventListener(function(isSelectionUpdate, caller) { var selectedIDs = []; for (var i = 0; i < selectionManager.selections.length; i++) { @@ -107,7 +107,8 @@ EntityListTool = function(shouldUseEditTabletApp) { emitJSONScriptEvent({ type: 'selectionUpdate', - selectedIDs: selectedIDs + selectedIDs: selectedIDs, + caller: caller }); }); @@ -224,7 +225,7 @@ EntityListTool = function(shouldUseEditTabletApp) { for (var i = 0; i < ids.length; i++) { entityIDs.push(ids[i]); } - selectionManager.setSelections(entityIDs); + selectionManager.setSelections(entityIDs, "entityList"); if (data.focus) { cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 843d3e986f..4302a531b5 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -136,7 +136,7 @@ SelectionManager = (function() { return that.selections.length > 0; }; - that.setSelections = function(entityIDs) { + that.setSelections = function(entityIDs, caller) { that.selections = []; for (var i = 0; i < entityIDs.length; i++) { var entityID = entityIDs[i]; @@ -144,7 +144,7 @@ SelectionManager = (function() { Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID); } - that._update(true); + that._update(true, caller); }; that.addEntity = function(entityID, toggleSelection) { @@ -477,7 +477,7 @@ SelectionManager = (function() { undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties); } - that._update = function(selectionUpdated) { + that._update = function(selectionUpdated, caller) { var properties = null; if (that.selections.length === 0) { that.localDimensions = null; @@ -542,7 +542,7 @@ SelectionManager = (function() { for (var j = 0; j < listeners.length; j++) { try { - listeners[j](selectionUpdated === true); + listeners[j](selectionUpdated === true, caller); } catch (e) { print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); } From 776b8b8fc7161060b037e12e661dedf122897235 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 9 Oct 2018 00:59:53 +0200 Subject: [PATCH 228/276] CR and style fixes --- scripts/system/edit.js | 42 +++++++++---------- scripts/system/html/js/entityList.js | 29 ++++++------- scripts/system/html/js/listView.js | 14 +++++-- scripts/system/libraries/entityList.js | 9 ++-- .../system/libraries/entitySelectionTool.js | 38 ++++++++--------- 5 files changed, 69 insertions(+), 63 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index b911541f79..0d63b92b63 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -385,7 +385,7 @@ var toolBar = (function () { Entities.editEntity(entityID, { position: position }); - selectionManager._update(); + selectionManager._update(false, this); } else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) { Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL); } @@ -397,9 +397,9 @@ var toolBar = (function () { properties.type + " would be out of bounds."); } - selectionManager.clearSelections(); + selectionManager.clearSelections(this); entityListTool.sendUpdate(); - selectionManager.setSelections([entityID]); + selectionManager.setSelections([entityID], this); Window.setFocus(); @@ -550,7 +550,7 @@ var toolBar = (function () { } deletedEntityTimer = Script.setTimeout(function () { if (entitiesToDelete.length > 0) { - selectionManager.removeEntities(entitiesToDelete); + selectionManager.removeEntities(entitiesToDelete, this); } entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); entitiesToDelete = []; @@ -866,7 +866,7 @@ var toolBar = (function () { gridTool.setVisible(false); grid.setEnabled(false); propertiesTool.setVisible(false); - selectionManager.clearSelections(); + selectionManager.clearSelections(this); cameraManager.disable(); selectionDisplay.disableTriggerMapping(); tablet.landscape = false; @@ -994,7 +994,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) { var entity = entityIconOverlayManager.findEntity(data.overlayID); if (entity !== null) { - selectionManager.setSelections([entity]); + selectionManager.setSelections([entity], this); } } } @@ -1141,7 +1141,7 @@ function mouseClickEvent(event) { if (result === null || result === undefined) { if (!event.isShifted) { - selectionManager.clearSelections(); + selectionManager.clearSelections(this); } return; } @@ -1193,9 +1193,9 @@ function mouseClickEvent(event) { } if (!event.isShifted) { - selectionManager.setSelections([foundEntity]); + selectionManager.setSelections([foundEntity], this); } else { - selectionManager.addEntity(foundEntity, true); + selectionManager.addEntity(foundEntity, true, this); } if (wantDebug) { @@ -1493,7 +1493,7 @@ function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) { } } } - selectionManager.setSelections(entities); + selectionManager.setSelections(entities, this); } } @@ -1633,7 +1633,7 @@ function deleteSelectedEntities() { } if (savedProperties.length > 0) { - SelectionManager.clearSelections(); + SelectionManager.clearSelections(this); pushCommandForSelections([], savedProperties); entityListTool.deleteEntities(deletedIDs); } @@ -1650,7 +1650,7 @@ function toggleSelectedEntitiesLocked() { }); } entityListTool.sendUpdate(); - selectionManager._update(); + selectionManager._update(false, this); } } @@ -1664,7 +1664,7 @@ function toggleSelectedEntitiesVisible() { }); } entityListTool.sendUpdate(); - selectionManager._update(); + selectionManager._update(false, this); } } @@ -1861,7 +1861,7 @@ function importSVO(importURL) { } if (isActive) { - selectionManager.setSelections(pastedEntityIDs); + selectionManager.setSelections(pastedEntityIDs, this); } } else { Window.notifyEditError("Can't import entities: entities would be out of bounds."); @@ -1909,7 +1909,7 @@ function deleteKey(value) { } function deselectKey(value) { if (value === 0) { // on release - selectionManager.clearSelections(); + selectionManager.clearSelections(this); } } function toggleKey(value) { @@ -2069,7 +2069,7 @@ function applyEntityProperties(data) { // We might be getting an undo while edit.js is disabled. If that is the case, don't set // our selections, causing the edit widgets to display. if (isActive) { - selectionManager.setSelections(selectedEntityIDs); + selectionManager.setSelections(selectedEntityIDs, this); } } @@ -2272,7 +2272,7 @@ var PropertiesTool = function (opts) { } } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } else if (data.type === 'parent') { parentSelectedEntities(); } else if (data.type === 'unparent') { @@ -2301,7 +2301,7 @@ var PropertiesTool = function (opts) { }); } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "moveAllToGrid") { if (selectionManager.hasSelection()) { @@ -2321,7 +2321,7 @@ var PropertiesTool = function (opts) { }); } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "resetToNaturalDimensions") { if (selectionManager.hasSelection()) { @@ -2342,7 +2342,7 @@ var PropertiesTool = function (opts) { } } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "previewCamera") { if (selectionManager.hasSelection()) { @@ -2360,7 +2360,7 @@ var PropertiesTool = function (opts) { }); } pushCommandForSelections(); - selectionManager._update(); + selectionManager._update(false, this); } } else if (data.action === "reloadClientScripts") { if (selectionManager.hasSelection()) { diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 0c87470f57..a75bd7e4b9 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -13,7 +13,7 @@ const DESCENDING_STRING = '▾'; const LOCKED_GLYPH = ""; const VISIBLE_GLYPH = ""; const TRANSPARENCY_GLYPH = ""; -const BAKED_GLYPH = "" +const BAKED_GLYPH = ""; const SCRIPT_GLYPH = "k"; const BYTES_PER_MEGABYTE = 1024 * 1024; const IMAGE_MODEL_NAME = 'default-image-model.fbx'; @@ -54,10 +54,10 @@ const COMPARE_ASCENDING = function(a, b) { } return 1; -} +}; const COMPARE_DESCENDING = function(a, b) { return COMPARE_ASCENDING(b, a); -} +}; // List of all entities var entities = []; @@ -156,22 +156,22 @@ function loaded() { }; elRefresh.onclick = function() { refreshEntities(); - } + }; elToggleLocked.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' })); - } + }; elToggleVisible.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' })); - } + }; elExport.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); - } + }; elPal.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' })); - } + }; elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); - } + }; elFilter.onkeyup = refreshEntityList; elFilter.onpaste = refreshEntityList; elFilter.onchange = onFilterChange; @@ -227,7 +227,7 @@ function loaded() { } } - updateSelectedEntities(selection); + updateSelectedEntities(selection, false); EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", @@ -289,7 +289,7 @@ function loaded() { hasScript: entity.hasScript, elRow: null, // if this entity has a visible row element assigned to it selected: false // if this entity is selected for edit regardless of having a visible row - } + }; entities.push(entityData); entitiesByID[entityData.id] = entityData; @@ -418,7 +418,7 @@ function loaded() { isBaked: document.querySelector('#entity-isBaked .sort-order'), drawCalls: document.querySelector('#entity-drawCalls .sort-order'), hasScript: document.querySelector('#entity-hasScript .sort-order'), - } + }; function setSortColumn(column) { PROFILE("set-sort-column", function() { if (currentSortColumn === column) { @@ -454,9 +454,6 @@ function loaded() { } function updateSelectedEntities(selectedIDs, autoScroll) { - // force autoScroll to be a boolean - autoScroll = !!autoScroll; - let notFound = false; // reset all currently selected entities and their rows first @@ -663,7 +660,7 @@ function loaded() { data = JSON.parse(data); if (data.type === "clearEntityList") { clearEntities(); - } else if (data.type === "selectionUpdate" && data.caller !== "entityList") { + } else if (data.type === "selectionUpdate") { let notFound = updateSelectedEntities(data.selectedIDs, true); if (notFound) { refreshEntities(); diff --git a/scripts/system/html/js/listView.js b/scripts/system/html/js/listView.js index d11452e111..62163ffe16 100644 --- a/scripts/system/html/js/listView.js +++ b/scripts/system/html/js/listView.js @@ -38,7 +38,7 @@ function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunctio this.lastRowShiftScrollTop = 0; this.initialize(); -}; +} ListView.prototype = { getNumRows: function() { @@ -153,9 +153,15 @@ ListView.prototype = { } }, - scrollToRow(rowIndex, lastRowIndex) { - lastRowIndex = lastRowIndex ? lastRowIndex : rowIndex; - let boundingTop = rowIndex * this.rowHeight; + /** + * Scrolls firstRowIndex with least effort, also tries to make the window include the other selections in case lastRowIndex is set. + * In the case that firstRowIndex and lastRowIndex are already within the visible bounds then nothing will happen. + * @param {number} firstRowIndex - The row that will be scrolled to. + * @param {number} lastRowIndex - The last index of the bound. + */ + scrollToRow: function (firstRowIndex, lastRowIndex) { + lastRowIndex = lastRowIndex ? lastRowIndex : firstRowIndex; + let boundingTop = firstRowIndex * this.rowHeight; let boundingBottom = (lastRowIndex * this.rowHeight) + this.rowHeight; if ((boundingBottom - boundingTop) > this.elTableScroll.clientHeight) { boundingBottom = boundingTop + this.elTableScroll.clientHeight; diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 04b19aa280..045990b99b 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -99,6 +99,10 @@ EntityListTool = function(shouldUseEditTabletApp) { }; selectionManager.addEventListener(function(isSelectionUpdate, caller) { + if (caller === that) { + // ignore events that we emitted from the entity list itself + return; + } var selectedIDs = []; for (var i = 0; i < selectionManager.selections.length; i++) { @@ -107,8 +111,7 @@ EntityListTool = function(shouldUseEditTabletApp) { emitJSONScriptEvent({ type: 'selectionUpdate', - selectedIDs: selectedIDs, - caller: caller + selectedIDs: selectedIDs }); }); @@ -225,7 +228,7 @@ EntityListTool = function(shouldUseEditTabletApp) { for (var i = 0; i < ids.length; i++) { entityIDs.push(ids[i]); } - selectionManager.setSelections(entityIDs, "entityList"); + selectionManager.setSelections(entityIDs, that); if (data.focus) { cameraManager.enable(); cameraManager.focus(selectionManager.worldPosition, diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 4302a531b5..73d84cf293 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -40,7 +40,7 @@ SelectionManager = (function() { Messages.messageReceived.connect(handleEntitySelectionToolUpdates); } - // FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES + // FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES function handleEntitySelectionToolUpdates(channel, message, sender) { if (channel !== 'entityToolUpdates') { return; @@ -63,7 +63,7 @@ SelectionManager = (function() { if (wantDebug) { print("setting selection to " + messageParsed.entityID); } - that.setSelections([messageParsed.entityID]); + that.setSelections([messageParsed.entityID], that); } } else if (messageParsed.method === "clearSelection") { if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { @@ -147,7 +147,7 @@ SelectionManager = (function() { that._update(true, caller); }; - that.addEntity = function(entityID, toggleSelection) { + that.addEntity = function(entityID, toggleSelection, caller) { if (entityID) { var idx = -1; for (var i = 0; i < that.selections.length; i++) { @@ -165,7 +165,7 @@ SelectionManager = (function() { } } - that._update(true); + that._update(true, caller); }; function removeEntityByID(entityID) { @@ -176,21 +176,21 @@ SelectionManager = (function() { } } - that.removeEntity = function (entityID) { + that.removeEntity = function (entityID, caller) { removeEntityByID(entityID); - that._update(true); + that._update(true, caller); }; - that.removeEntities = function(entityIDs) { + that.removeEntities = function(entityIDs, caller) { for (var i = 0, length = entityIDs.length; i < length; i++) { removeEntityByID(entityIDs[i]); } - that._update(true); + that._update(true, caller); }; - that.clearSelections = function() { + that.clearSelections = function(caller) { that.selections = []; - that._update(true); + that._update(true, caller); }; that.addChildrenEntities = function(parentEntityID, entityList) { @@ -985,7 +985,7 @@ SelectionDisplay = (function() { that.pressedHand = NO_HAND; that.triggered = function() { return that.triggeredHand !== NO_HAND; - } + }; function pointingAtDesktopWindowOrTablet(hand) { var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtDesktopWindowRight) || @@ -1032,7 +1032,7 @@ SelectionDisplay = (function() { that.disableTriggerMapping = function() { that.triggerClickMapping.disable(); that.triggerPressMapping.disable(); - } + }; Script.scriptEnding.connect(that.disableTriggerMapping); // FUNCTION DEF(s): Intersection Check Helpers @@ -1234,7 +1234,7 @@ SelectionDisplay = (function() { if (wantDebug) { print(" Trigger SelectionManager::update"); } - SelectionManager._update(); + SelectionManager._update(false, that); if (wantDebug) { print("=============== eST::MouseMoveEvent END ======================="); @@ -1299,7 +1299,7 @@ SelectionDisplay = (function() { lastMouseEvent.isControl = event.isControl; lastMouseEvent.isAlt = event.isAlt; activeTool.onMove(lastMouseEvent); - SelectionManager._update(); + SelectionManager._update(false, this); } }; @@ -1315,7 +1315,7 @@ SelectionDisplay = (function() { lastMouseEvent.isControl = event.isControl; lastMouseEvent.isAlt = event.isAlt; activeTool.onMove(lastMouseEvent); - SelectionManager._update(); + SelectionManager._update(false, this); } }; @@ -2179,7 +2179,7 @@ SelectionDisplay = (function() { } } - SelectionManager._update(); + SelectionManager._update(false, this); } }); } @@ -2301,7 +2301,7 @@ SelectionDisplay = (function() { previousPickRay = pickRay; - SelectionManager._update(); + SelectionManager._update(false, this); } }); } @@ -2488,7 +2488,7 @@ SelectionDisplay = (function() { previousPickRay = pickRay; - SelectionManager._update(); + SelectionManager._update(false, this); } }); } @@ -2599,7 +2599,7 @@ SelectionDisplay = (function() { previousPickRay = pickRay; - SelectionManager._update(); + SelectionManager._update(false, this); } }); } From 8e1cfb5a067bde0d5676ba3d1024d53cc17ba4ec Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Oct 2018 23:03:26 +0200 Subject: [PATCH 229/276] fix double click selection --- scripts/system/html/js/entityList.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index a75bd7e4b9..55871533e2 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -239,11 +239,16 @@ function loaded() { } function onRowDoubleClicked() { + let selection = [this.dataset.entityID]; + updateSelectedEntities(selection, false); + EventBridge.emitWebEvent(JSON.stringify({ type: "selectionUpdate", focus: true, - entityIds: [this.dataset.entityID], + entityIds: selection, })); + + refreshFooter(); } function decimalMegabytes(number) { From 5f35a324805d68c97748f5ac65c8b7e97282e15d Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 22 Oct 2018 23:09:41 +0200 Subject: [PATCH 230/276] remove duplicate calls to refresh footer --- scripts/system/html/js/entityList.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 55871533e2..62c8d6ca6f 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -234,8 +234,6 @@ function loaded() { focus: false, entityIds: selection, })); - - refreshFooter(); } function onRowDoubleClicked() { @@ -247,8 +245,6 @@ function loaded() { focus: true, entityIds: selection, })); - - refreshFooter(); } function decimalMegabytes(number) { From 5c1972e3f411129691fe2a061c89f421933c0974 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 24 Oct 2018 12:40:44 -0700 Subject: [PATCH 231/276] fix fallback properties --- scripts/system/html/js/entityProperties.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index a3f986ad53..db3424bbd9 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -2935,7 +2935,21 @@ function loaded() { continue; } - if (!propertyValue && propertyData.fallbackProperty !== undefined) { + let isPropertyNotNumber = false; + switch (propertyData.type) { + case 'number': + case 'slider': + isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null; + break; + case 'vec3': + case 'vec2': + isPropertyNotNumber = isNaN(propertyValue.x) || propertyValue.x === null; + break; + case 'color': + isPropertyNotNumber = isNaN(propertyValue.red) || propertyValue.red === null; + break; + } + if (isPropertyNotNumber && propertyData.fallbackProperty !== undefined) { propertyValue = getPropertyValue(propertyData.fallbackProperty); } @@ -3000,9 +3014,6 @@ function loaded() { break; } case 'color': { - if (!propertyValue.red && propertyData.fallbackProperty !== undefined) { - propertyValue = getPropertyValue(propertyData.fallbackProperty); - } property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; From a3c42363cc74d9e6d8dd41060e137de1f14013e1 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 24 Oct 2018 12:43:01 -0700 Subject: [PATCH 232/276] please for the love of god --- .../avatars-renderer/src/avatars-renderer/Avatar.cpp | 10 +++++----- .../avatars-renderer/src/avatars-renderer/Avatar.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 4a06a5252a..86f5bd69a5 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -707,10 +707,10 @@ static TextRenderer3D* textRenderer(TextRendererType type) { return displayNameRenderer; } -void Avatar::metaBlendshapeOperator(int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, - const render::ItemIDs& subItemIDs) { +void Avatar::metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector& blendshapeOffsets, + const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs) { render::Transaction transaction; - transaction.updateItem(_renderItemID, [blendshapeNumber, blendshapeOffsets, blendedMeshSizes, + transaction.updateItem(renderItemID, [blendshapeNumber, blendshapeOffsets, blendedMeshSizes, subItemIDs](AvatarData& avatar) { auto avatarPtr = dynamic_cast(&avatar); if (avatarPtr) { @@ -730,7 +730,7 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc _renderBound = getBounds(); transaction.resetItem(_renderItemID, avatarPayloadPointer); using namespace std::placeholders; - _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4)); + _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, _renderItemID, _1, _2, _3, _4)); _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); _skeletonModel->setCanCastShadow(true); @@ -954,7 +954,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) { _skeletonModel->removeFromScene(scene, transaction); using namespace std::placeholders; - _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4)); + _skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, _renderItemID, _1, _2, _3, _4)); _skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS); _skeletonModel->setGroupCulled(true); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index ca063a7026..9a4b9bb6b6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -626,8 +626,8 @@ protected: LoadingStatus _loadingStatus { LoadingStatus::NoModel }; - void metaBlendshapeOperator(int blendshapeNumber, const QVector& blendshapeOffsets, const QVector& blendedMeshSizes, - const render::ItemIDs& subItemIDs); + static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector& blendshapeOffsets, + const QVector& blendedMeshSizes, const render::ItemIDs& subItemIDs); }; #endif // hifi_Avatar_h From b8cd5045c79c8649e28c2f7a6728dcb350626189 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 24 Oct 2018 12:54:09 -0700 Subject: [PATCH 233/276] Extra joints on soft entities keep their local transform --- .../avatars-renderer/src/avatars-renderer/Avatar.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 6770cd7f96..36d336f910 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -488,8 +488,8 @@ void Avatar::relayJointDataToChildren() { glm::quat jointRotation; glm::vec3 jointTranslation; if (avatarJointIndex < 0) { - jointRotation = modelEntity->getAbsoluteJointRotationInObjectFrame(jointIndex); - jointTranslation = modelEntity->getAbsoluteJointTranslationInObjectFrame(jointIndex); + jointRotation = modelEntity->getLocalJointRotation(jointIndex); + jointTranslation = modelEntity->getLocalJointTranslation(jointIndex); map.push_back(-1); } else { int jointIndex = getJointIndex(jointName); @@ -512,8 +512,8 @@ void Avatar::relayJointDataToChildren() { jointRotation = getJointRotation(avatarJointIndex); jointTranslation = getJointTranslation(avatarJointIndex); } else { - jointRotation = modelEntity->getAbsoluteJointRotationInObjectFrame(jointIndex); - jointTranslation = modelEntity->getAbsoluteJointTranslationInObjectFrame(jointIndex); + jointRotation = modelEntity->getLocalJointRotation(jointIndex); + jointTranslation = modelEntity->getLocalJointTranslation(jointIndex); } modelEntity->setLocalJointRotation(jointIndex, jointRotation); modelEntity->setLocalJointTranslation(jointIndex, jointTranslation); From b6f259fc42aa7ed03b2001056e46c2b71bfc2d83 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 24 Oct 2018 13:17:45 -0700 Subject: [PATCH 234/276] Fix incorrect merge-resolution --- libraries/avatars/src/AvatarData.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index e08f7fb6f8..7f42289a9b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -419,11 +419,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) { auto startSection = destinationBuffer; - if (_overrideGlobalPosition) { - AVATAR_MEMCPY(_globalPositionOverride); - } else { - AVATAR_MEMCPY(_globalPosition); - } + AVATAR_MEMCPY(_globalPosition); int numBytes = destinationBuffer - startSection; From 4d34b12f06c9107e1d5c282711b6a8a6b7942d9b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 21 Oct 2018 18:48:38 -0700 Subject: [PATCH 235/276] rather than do checkDevices to find new audio devices every 2 seconds, do each check 2 seconds after the previous one has completed. This is done to avoid swamping the thread pool when the check takes a long time --- libraries/audio-client/src/AudioClient.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index d00bc29054..bdd6d0edc1 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -244,13 +244,20 @@ AudioClient::AudioClient() : // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash getAvailableDevices(QAudio::AudioInput); getAvailableDevices(QAudio::AudioOutput); - + // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); - connect(_checkDevicesTimer, &QTimer::timeout, this, [this] { - QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkDevices(); }); - }); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; + connect(_checkDevicesTimer, &QTimer::timeout, this, [=] { + QtConcurrent::run(QThreadPool::globalInstance(), [=] { + checkDevices(); + // On some systems (Ubuntu) checking all the audio devices can take more than 2 seconds. To + // avoid consuming all of the thread pool, don't start the check interval until the previous + // check has completed. + QMetaObject::invokeMethod(_checkDevicesTimer, "start", Q_ARG(int, DEVICE_CHECK_INTERVAL_MSECS)); + }); + }); + _checkDevicesTimer->setSingleShot(true); _checkDevicesTimer->start(DEVICE_CHECK_INTERVAL_MSECS); // start a thread to detect peak value changes From a88678fb70a124632ae29cbe36980ab3a2608e2d Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 24 Oct 2018 15:07:47 -0700 Subject: [PATCH 236/276] fix bad css merge, add particle property debounce --- scripts/system/html/css/edit-style.css | 2 +- scripts/system/html/js/entityProperties.js | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 5214dc0c3e..2db7fce065 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1107,7 +1107,7 @@ body#entity-list-body { position: relative; /* New positioning context. */ } -#search-area { +#filter-area { padding-right: 168px; padding-bottom: 24px; } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index db3424bbd9..676ecaf289 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1304,6 +1304,7 @@ const JSON_EDITOR_ROW_DIV_INDEX = 2; var elGroups = {}; var properties = {}; var colorPickers = {}; +var particlePropertyUpdates = {}; var selectedEntityProperties; var lastEntityID = null; @@ -1534,9 +1535,23 @@ function updateProperty(originalPropertyName, propertyValue) { } else { propertyUpdate[originalPropertyName] = propertyValue; } - updateProperties(propertyUpdate); + // queue up particle property changes with the debounced sync to avoid + // causing particle emitting to reset each frame when updating values + if (properties[originalPropertyName].isParticleProperty) { + Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) { + particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey]; + }); + particleSyncDebounce(); + } else { + updateProperties(propertyUpdate); + } } +var particleSyncDebounce = _.debounce(function () { + updateProperties(particlePropertyUpdates); + particlePropertyUpdates = {}; +}, DEBOUNCE_TIMEOUT); + function updateProperties(propertiesToUpdate) { EventBridge.emitWebEvent(JSON.stringify({ id: lastEntityID, @@ -2717,7 +2732,8 @@ function loaded() { let property = { data: propertyData, elementID: propertyElementID, - name: propertyName, + name: propertyName, + isParticleProperty: group.id.includes("particles"), elProperty: elProperty }; properties[propertyID] = property; From 5c5a5c9cd2a4c25d2c061819b838cee6d8da41f5 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 24 Oct 2018 16:14:05 -0700 Subject: [PATCH 237/276] Add optional default scripts log suppression --- scripts/developer/debugging/debugWindow.js | 36 +++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js index 993ca49a40..84bd3c323c 100644 --- a/scripts/developer/debugging/debugWindow.js +++ b/scripts/developer/debugging/debugWindow.js @@ -19,6 +19,23 @@ if (scripts.length >= 2) { return; } +var SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME = "Developer" +var SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME = "Suppress messages from default scripts in Debug Window"; +var DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS = 'debugWindowSuppressDefaultScripts'; +var suppressDefaultScripts = Settings.getValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS, false) +Menu.addMenuItem({ + menuName: SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME, + menuItemName: SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME, + isCheckable: true, + isChecked: suppressDefaultScripts +}); + +Menu.menuItemEvent.connect(function(menuItem) { + if (menuItem === SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME) { + suppressDefaultScripts = Menu.isOptionChecked(SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME); + } +}); + // Set up the qml ui var qml = Script.resolvePath('debugWindow.qml'); @@ -61,17 +78,24 @@ window.visibleChanged.connect(function() { window.closed.connect(function () { Script.stop(); }); +function shouldLogMessage(scriptFileName) { + return !suppressDefaultScripts + || (scriptFileName !== "defaultScripts.js" && scriptFileName != "controllerScripts.js"); +} + var getFormattedDate = function() { var date = new Date(); return date.getMonth() + "/" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); }; var sendToLogWindow = function(type, message, scriptFileName) { - var typeFormatted = ""; - if (type) { - typeFormatted = type + " - "; + if (shouldLogMessage(scriptFileName)) { + var typeFormatted = ""; + if (type) { + typeFormatted = type + " - "; + } + window.sendToQml("[" + getFormattedDate() + "] " + "[" + scriptFileName + "] " + typeFormatted + message); } - window.sendToQml("[" + getFormattedDate() + "] " + "[" + scriptFileName + "] " + typeFormatted + message); }; ScriptDiscoveryService.printedMessage.connect(function(message, scriptFileName) { @@ -95,6 +119,10 @@ ScriptDiscoveryService.clearDebugWindow.connect(function() { }); Script.scriptEnding.connect(function () { + Settings.setValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS, + Menu.isOptionChecked(SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME)); + Menu.removeMenuItem(SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME, SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME); + var geometry = JSON.stringify({ x: window.position.x, y: window.position.y, From a3b874f9aa77c3c63b106054be3fee8bb6bfe391 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 18 Oct 2018 01:14:13 +0200 Subject: [PATCH 238/276] property tooltips --- .../system/assets/data/createAppTooltips.json | 458 ++++++++++++++++++ scripts/system/edit.js | 5 + scripts/system/html/css/edit-style.css | 22 + scripts/system/html/entityProperties.html | 1 + scripts/system/html/js/createAppTooltip.js | 84 ++++ scripts/system/html/js/entityProperties.js | 8 +- 6 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 scripts/system/assets/data/createAppTooltips.json create mode 100644 scripts/system/html/js/createAppTooltip.js diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json new file mode 100644 index 0000000000..f8c037297f --- /dev/null +++ b/scripts/system/assets/data/createAppTooltips.json @@ -0,0 +1,458 @@ +{ + "shape": { + "tooltip": "The shape of this entity's geometry." + }, + "color": { + "tooltip": "The RGB value of this entity." + }, + "materialURL": { + "tooltip": "The URL of a JSON file containing the material. Use this to change the entity's look. " + }, + "text": { + "tooltip": "The text to display on the entity." + }, + "textColor": { + "tooltip": "The color of the text." + }, + "backgroundColor": { + "tooltip": "The color of the background." + }, + "lineHeight": { + "tooltip": "The height of each line of text. This determines the size of the text." + }, + "faceCamera": { + "tooltip": "If enabled, the entity follows the camera of each user, creating a billboard effect." + }, + "flyingAllowed": { + "tooltip": "If enabled, users can fly in the zone." + }, + "ghostingAllowed": { + "tooltip": "If enabled, users with avatar collisions turned off will not collide with content in the zone." + }, + "filterURL": { + "tooltip": "The URL of a JS file that checks for changes to entity properties within the zone. Runs periodically." + }, + "keyLightMode": { + "tooltip": "Configures the key light in the zone. This light is directional." + }, + "keyLightColor": { + "tooltip": "The color of the key light." + }, + "keyLight.intensity": { + "tooltip": "The intensity of the key light." + }, + "keyLight.direction.y": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its y axis." + }, + "keyLight.direction.x": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its x axis." + }, + "keyLight.castShadows": { + "tooltip": "If enabled, shadows are cast. The entity or avatar casting the shadow must also have Cast Shadows enabled." + }, + "skyboxMode": { + "tooltip": "Configures the skybox in the zone. The skybox is a cube map image." + }, + "skybox.color": { + "tooltip": "If the URL is blank, this changes the color of the sky, otherwise it modifies the color of the skybox." + }, + "skybox.url": { + "tooltip": "A cube map image that is used to render the sky." + }, + "ambientLightMode": { + "tooltip": "Configures the ambient light in the zone. Use this if you want your skybox to reflect light on the content." + }, + "ambientLight.ambientIntensity": { + "tooltip": "The intensity of the ambient light." + }, + "ambientLight.ambientURL": { + "tooltip": "A cube map image that defines the color of the light coming from each direction." + }, + "hazeMode": { + "tooltip": "Configures the haze in the scene." + }, + "haze.hazeRange": { + "tooltip": "How far the haze extends out. This is measured in meters." + }, + "haze.hazeAltitudeEffect": { + "tooltip": "If enabled, this adjusts the haze intensity as it gets higher." + }, + "haze.hazeBaseRef": { + "tooltip": "The base of the altitude range. Measured in entity space." + }, + "haze.hazeCeiling": { + "tooltip": "The ceiling of the altitude range. Measured in entity space." + }, + "haze.hazeColor": { + "tooltip": "The color of the haze." + }, + "haze.hazeBackgroundBlend": { + "tooltip": "How much the skybox shows through the haze. The higher the value, the more it shows through." + }, + "haze.hazeEnableGlare": { + "tooltip": "If enabled, a glare is enabled on the skybox, based on the key light." + }, + "haze.hazeGlareColor": { + "tooltip": "The color of the glare based on the key light." + }, + "haze.hazeGlareAngle": { + "tooltip": "The angular size of the glare and how much it encompasses the skybox, based on the key light." + }, + "bloomMode": { + "tooltip": "Configures how much bright areas of the scene glow." + }, + "bloom.bloomIntensity": { + "tooltip": "The intensity, or brightness, of the bloom effect." + }, + "bloom.bloomThreshold": { + "tooltip": "The cutoff of the bloom. The higher the value, the more only bright areas of the scene will glow." + }, + "bloom.bloomSize": { + "tooltip": "The radius of bloom. The higher the value, the larger the bloom." + }, + "modelURL": { + "tooltip": "A mesh model from an FBX or OBJ file." + }, + "shapeType": { + "tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides." + }, + "compoundShapeURL": { + "tooltip": "The OBJ file to use for the compound shape if Collision Shape is \"compound\"." + }, + "animation.url": { + "tooltip": "An animation to play on the model." + }, + "animation.running": { + "tooltip": "If enabled, the animation on the model will play automatically." + }, + "animation.allowTranslation": { + "tooltip": "If enabled, this allows an entity to move in space during an animation." + }, + "animation.loop": { + "tooltip": "If enabled, then the animation will continuously repeat." + }, + "animation.hold": { + "tooltip": "If enabled, then rotations and translations of the last frame played are maintained when the animation stops." + }, + "animation.currentFrame": { + "tooltip": "The current frame being played in the animation." + }, + "animation.firstFrame": { + "tooltip": "The first frame to play in the animation." + }, + "animation.lastFrame": { + "tooltip": "The last frame to play in the animation." + }, + "animation.fps": { + "tooltip": "The speed of the animation." + }, + "textures": { + "tooltip": "The URL of a JPG or PNG image file to display for each particle." + }, + "originalTextures": { + "tooltip": "A JSON string containing the original texture used on the model." + }, + "imageUrl": { + "tooltip": "The URL for the image source.", + "jsPropertyName": "textures" + }, + "sourceUrl": { + "tooltip": "The URL for the web page source." + }, + "dpi": { + "tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame." + }, + "isEmitting": { + "tooltip": "If enabled, then particles are emitted." + }, + "lifespan": { + "tooltip": "How long each particle lives, measured in seconds." + }, + "maxParticles": { + "tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones." + }, + "emitRate": { + "tooltip": "The number of particles per second to emit." + }, + "emitSpeed": { + "tooltip": "The speed that each particle is emitted at, measured in m/s." + }, + "speedSpread": { + "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds." + }, + "emitDimensions": { + "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." + }, + "emitOrientation": { + "tooltip": "The orientation of particle emission relative to the entity's axes." + }, + "emitRadiusStart": { + "tooltip": "The inner limit radius in dimensions that the particles start emitting from." + }, + "emitterShouldTrail": { + "tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not." + }, + "particleRadius": { + "tooltip": "The size of each particle." + }, + "radiusStart": { + "tooltip": "" + }, + "radiusFinish": { + "tooltip": "" + }, + "radiusSpread": { + "tooltip": "The spread in size that each particle is given, resulting in a variety of sizes." + }, + "particleColor": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, + "colorSpread": { + "tooltip": "The spread in color that each particle is given, resulting in a variety of colors." + }, + "alpha": { + "tooltip": "The alpha of each particle." + }, + "alphaStart": { + "tooltip": "" + }, + "alphaFinish": { + "tooltip": "" + }, + "alphaSpread": { + "tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas." + }, + "emitAcceleration": { + "tooltip": "The acceleration that is applied to each particle during its lifetime." + }, + "accelerationSpread": { + "tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations." + }, + "particleSpin": { + "tooltip": "The spin of each particle in the system." + }, + "spinStart": { + "tooltip": "" + }, + "spinFinish": { + "tooltip": "" + }, + "spinSpread": { + "tooltip": "The spread in spin that each particle is given, resulting in a variety of spins." + }, + "rotateWithEntity": { + "tooltip": "If enabled, each particle will spin relative to the roation of the entity as a whole." + }, + "azimuthStart": { + "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "azimuthFinish": { + "tooltip": "" + }, + "polarStart": { + "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "polarFinish": { + "tooltip": "" + }, + "lightColor": { + "tooltip": "The color of the light emitted.", + "jsPropertyName": "color" + }, + "intensity": { + "tooltip": "The brightness of the light." + }, + "falloffRadius": { + "tooltip": "The distance from the light's center where the intensity is reduced." + }, + "isSpotlight": { + "tooltip": "If enabled, then the light is directional, otherwise the light is a point light which emits light in all directions." + }, + "exponent": { + "tooltip": "Affects the softness of the spotlight beam; the higher the value, the softer the beam." + }, + "cutoff": { + "tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam." + }, + "id": { + "tooltip": "The unique identifier of this entity." + }, + "name": { + "tooltip": "The name of this entity." + }, + "description": { + "tooltip": "Use this field to describe the entity." + }, + "position": { + "tooltip": "The global position of this entity." + }, + "rotation": { + "tooltip": "The rotation of the entity with respect to world coordinates." + }, + "dimensions": { + "tooltip": "The global dimensions of this entity." + }, + "scale": { + "tooltip": "The global scaling of this entity,", + "skipJSProperty": true + }, + "registrationPoint": { + "tooltip": "The point in the entity at which the entity is rotated about." + }, + "collisionless": { + "tooltip": "If enabled, this entity will collide with other entities or avatars." + }, + "dynamic": { + "tooltip": "If enabled, this entity has collisions associated with it that can affect its movement." + }, + "collidesWithStatic": { + "tooltip": "If enabled, this entity will collide with other non-moving, static entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithDynamic": { + "tooltip": "If enabled, this entity will collide with other dynamic entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithKinematic": { + "tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).", + "jsPropertyName": "collidesWith" + }, + "collidesWithOtherAvatars": { + "tooltip": "If enabled, this entity will collide with other user's avatars.", + "jsPropertyName": "collidesWith" + }, + "collisionSoundURL": { + "tooltip": "The URL of a sound to play when the entity collides with something else." + }, + "grabbable": { + "tooltip": "If enabled, this entity will allow grabbing input and will be moveable.", + "jsPropertyName": "userData[\"grabbableKey\"][\"grabbable\"]" + }, + "triggerable": { + "tooltip": "If enabled, the collider on this entity is used for triggering events.", + "jsPropertyName": "userData[\"grabbableKey\"][\"triggerable\"]" + }, + "cloneable": { + "tooltip": "If enabled, this entity can be duplicated." + }, + "cloneLifetime": { + "tooltip": "The lifetime for clones of this entity." + }, + "cloneLimit": { + "tooltip": "The total number of clones of this entity that can exist in the domain at any given time." + }, + "cloneDynamic": { + "tooltip": "If enabled, then clones created from this entity will be dynamic, allowing the clone to collide." + }, + "cloneAvatarEntity": { + "tooltip": "If enabled, then clones created from this entity will be created as avatar entities." + }, + "ignoreIK": { + "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand.", + "jsPropertyName": "userData[\"grabbableKey\"][\"ignoreIK\"]" + }, + "canCastShadow": { + "tooltip": "If enabled, this geometry of this entity casts shadows when a shadow-casting light source shines on it." + }, + "parentID": { + "tooltip": "The ID of the entity or avatar that this entity is parented to." + }, + "parentJointIndex": { + "tooltip": "If the entity is parented to an avatar, this joint defines where on the avatar the entity is parented." + }, + "href": { + "tooltip": "The URL that will be opened when a user clicks on this entity. Useful for web pages and portals." + }, + "script": { + "tooltip": "The URL to an external JS file to add behaviors to the client." + }, + "serverScripts": { + "tooltip": "The URL to an external JS file to add behaviors to the server." + }, + "serverScriptsStatus": { + "tooltip": "The status of the server script, if provided. This shows if it's running or has an error.", + "skipJSProperty": true + }, + "hasLifetime": { + "tooltip": "If enabled, the entity will disappear after a certain amount of time specified by Lifetime.", + "jsPropertyName": "lifetime" + }, + "lifetime": { + "tooltip": "The time this entity will exist in the environment for." + }, + "userData": { + "tooltip": "Used to store extra data about the entity in JSON format." + }, + "velocity": { + "tooltip": "The linear velocity vector of the entity. The velocity at which this entity moves forward in space." + }, + "damping": { + "tooltip": "The linear damping to slow down the linear velocity of an entity over time." + }, + "angularVelocity": { + "tooltip": "The angular velocity of the entity in rad/s with respect to its axes, about its pivot point." + }, + "angularDamping": { + "tooltip": "The angular damping to slow down the angular velocity of an entity over time." + }, + "restitution": { + "tooltip": "If enabled, the entity can bounce against other objects that also have Bounciness." + }, + "friction": { + "tooltip": "The friction applied to slow down an entity when it's moving against another entity." + }, + "density": { + "tooltip": "The density of the entity. The higher the density, the harder the entity is to move." + }, + "gravity": { + "tooltip": "The acceleration due to gravity that the entity should move with, in world space." + }, + "acceleration": { + "tooltip": "A acceleration that the entity should move with, in world space." + }, + "createModel": { + "tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.", + "skipJSProperty": true + }, + "createShape": { + "tooltip": "An entity that has many different primitive shapes.", + "skipJSProperty": true + }, + "createLight": { + "tooltip": "An entity that emits light.", + "skipJSProperty": true + }, + "createText": { + "tooltip": "An entity that displays text on a panel.", + "skipJSProperty": true + }, + "createImage": { + "tooltip": "An entity that displays an image on a panel.", + "skipJSProperty": true + }, + "createWeb": { + "tooltip": "An entity that displays a web page on a panel.", + "skipJSProperty": true + }, + "createZone": { + "tooltip": "An entity that can be used for skyboxes, lighting, and can constrain or change avatar behaviors.", + "skipJSProperty": true + }, + "createParticle": { + "tooltip": "An entity that emits particles.", + "skipJSProperty": true + }, + "createMaterial": { + "tooltip": "An entity that creates a material that can be attached to a Shape or Model.", + "skipJSProperty": true + }, + "useAssetServer": { + "tooltip": "A server that hosts content and assets. You can't take items that are hosted here into other domains.", + "skipJSProperty": true + }, + "importNewEntity": { + "tooltip": "Import a local or hosted file that can be used across domains.", + "skipJSProperty": true + } +} diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 9f4ec3c62b..c6432ac2f9 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2481,6 +2481,11 @@ var PropertiesTool = function (opts) { } } else if (data.type === "propertiesPageReady") { updateSelections(true); + } else if (data.type === "tooltipsRequest") { + emitScriptEvent({ + type: 'tooltipsReply', + tooltips: Script.require('./assets/data/createAppTooltips.json') + }); } }; diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 2db7fce065..4ad886f1b7 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1598,3 +1598,25 @@ input.rename-entity { padding-left: 2px; } +.createAppTooltip { + position: absolute; + background: #6a6a6a; + border: 1px solid black; + width: 258px; + min-height: 20px; + padding: 5px; +} + +.createAppTooltip .createAppTooltipDescription { + font-size: 12px; + font-style: italic; + color: #ffffff; +} + +.createAppTooltip .createAppTooltipJSAttribute { + font-size: 10px; + color: #000000; + bottom: 0; + margin-top: 5px; +} + diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 03b034fc83..b56ad346e2 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -21,6 +21,7 @@ + diff --git a/scripts/system/html/js/createAppTooltip.js b/scripts/system/html/js/createAppTooltip.js new file mode 100644 index 0000000000..10ad73e1ee --- /dev/null +++ b/scripts/system/html/js/createAppTooltip.js @@ -0,0 +1,84 @@ +// createAppTooltip.js +// +// Created by Thijs Wenker on 17 Oct 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 + +const CREATE_APP_TOOLTIP_OFFSET = 20; + +function CreateAppTooltip() { + this._tooltipData = null; + this._tooltipDiv = null; +} + +CreateAppTooltip.prototype = { + _tooltipData: null, + _tooltipDiv: null, + + _removeTooltipIfExists: function() { + if (this._tooltipDiv !== null) { + this._tooltipDiv.remove(); + this._tooltipDiv = null; + } + }, + + setTooltipData: function(tooltipData) { + this._tooltipData = tooltipData; + }, + + registerTooltipElement: function(element, tooltipID) { + element.addEventListener("mouseover", function() { + + this._removeTooltipIfExists(); + + let tooltipData = this._tooltipData[tooltipID]; + + if (!tooltipData || tooltipData.tooltip === "") { + return; + } + + let elementRect = element.getBoundingClientRect(); + let elTip = document.createElement("div"); + elTip.className = "createAppTooltip"; + + let elTipDescription = document.createElement("div"); + elTipDescription.className = "createAppTooltipDescription"; + elTipDescription.innerText = tooltipData.tooltip; + elTip.appendChild(elTipDescription); + + let jsAttribute = tooltipID; + if (tooltipData.jsPropertyName) { + jsAttribute = tooltipData.jsPropertyName; + } + + if (!tooltipData.skipJSProperty) { + let elTipJSAttribute = document.createElement("div"); + elTipJSAttribute.className = "createAppTooltipJSAttribute"; + elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; + elTip.appendChild(elTipJSAttribute); + } + + document.body.appendChild(elTip); + + let elementTop = window.pageYOffset + elementRect.top; + + let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + + if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { + // show above when otherwise out of bounds + elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; + } else { + // show tooltip on below by default + elTip.style.top = desiredTooltipTop; + } + elTip.style.left = window.pageXOffset + elementRect.left; + + this._tooltipDiv = elTip; + }.bind(this), false); + element.addEventListener("mouseout", function() { + this._removeTooltipIfExists(); + }.bind(this), false); + } +}; diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 676ecaf289..59c2ba702b 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1307,6 +1307,7 @@ var colorPickers = {}; var particlePropertyUpdates = {}; var selectedEntityProperties; var lastEntityID = null; +var createAppTooltip = new CreateAppTooltip(); function debugPrint(message) { EventBridge.emitWebEvent( @@ -2659,7 +2660,7 @@ function showParentMaterialNameBox(number, elNumber, elString) { function loaded() { - openEventBridge(function() { + openEventBridge(function() { let elPropertiesList = document.getElementById("properties-list"); GROUPS.forEach(function(group) { @@ -2728,6 +2729,8 @@ function loaded() { let elLabel = document.createElement('label'); elLabel.innerText = propertyData.label; elLabel.setAttribute("for", propertyElementID); + + createAppTooltip.registerTooltipElement(elLabel, propertyID); let property = { data: propertyData, @@ -3150,6 +3153,8 @@ function loaded() { activeElement.select(); } } + } else if (data.type === 'tooltipsReply') { + createAppTooltip.setTooltipData(data.tooltips); } }); } @@ -3381,5 +3386,6 @@ function loaded() { setTimeout(function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'propertiesPageReady' })); + EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' })); }, 1000); } From 358b0b76d1f869882b02eead537d6a20e70f2d41 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 19 Oct 2018 16:32:38 +0200 Subject: [PATCH 239/276] tooltip delay of 500ms --- .../system/assets/data/createAppTooltips.json | 2 +- scripts/system/html/js/createAppTooltip.js | 76 +++++++++++-------- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index f8c037297f..7cc9e0a97e 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -294,7 +294,7 @@ "tooltip": "The global dimensions of this entity." }, "scale": { - "tooltip": "The global scaling of this entity,", + "tooltip": "The global scaling of this entity.", "skipJSProperty": true }, "registrationPoint": { diff --git a/scripts/system/html/js/createAppTooltip.js b/scripts/system/html/js/createAppTooltip.js index 10ad73e1ee..edd0f6366a 100644 --- a/scripts/system/html/js/createAppTooltip.js +++ b/scripts/system/html/js/createAppTooltip.js @@ -7,17 +7,25 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html const CREATE_APP_TOOLTIP_OFFSET = 20; +const TOOLTIP_DELAY = 500; // ms function CreateAppTooltip() { this._tooltipData = null; this._tooltipDiv = null; + this._delayTimeout = null; } CreateAppTooltip.prototype = { _tooltipData: null, _tooltipDiv: null, + _delayTimeout: null, _removeTooltipIfExists: function() { + if (this._delayTimeout !== null) { + window.clearTimeout(this._delayTimeout); + this._delayTimeout = null; + } + if (this._tooltipDiv !== null) { this._tooltipDiv.remove(); this._tooltipDiv = null; @@ -33,49 +41,51 @@ CreateAppTooltip.prototype = { this._removeTooltipIfExists(); - let tooltipData = this._tooltipData[tooltipID]; + this._delayTimeout = window.setTimeout(function() { + let tooltipData = this._tooltipData[tooltipID]; - if (!tooltipData || tooltipData.tooltip === "") { - return; - } + if (!tooltipData || tooltipData.tooltip === "") { + return; + } - let elementRect = element.getBoundingClientRect(); - let elTip = document.createElement("div"); - elTip.className = "createAppTooltip"; + let elementRect = element.getBoundingClientRect(); + let elTip = document.createElement("div"); + elTip.className = "createAppTooltip"; - let elTipDescription = document.createElement("div"); - elTipDescription.className = "createAppTooltipDescription"; - elTipDescription.innerText = tooltipData.tooltip; - elTip.appendChild(elTipDescription); + let elTipDescription = document.createElement("div"); + elTipDescription.className = "createAppTooltipDescription"; + elTipDescription.innerText = tooltipData.tooltip; + elTip.appendChild(elTipDescription); - let jsAttribute = tooltipID; - if (tooltipData.jsPropertyName) { - jsAttribute = tooltipData.jsPropertyName; - } + let jsAttribute = tooltipID; + if (tooltipData.jsPropertyName) { + jsAttribute = tooltipData.jsPropertyName; + } - if (!tooltipData.skipJSProperty) { - let elTipJSAttribute = document.createElement("div"); - elTipJSAttribute.className = "createAppTooltipJSAttribute"; - elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; - elTip.appendChild(elTipJSAttribute); - } + if (!tooltipData.skipJSProperty) { + let elTipJSAttribute = document.createElement("div"); + elTipJSAttribute.className = "createAppTooltipJSAttribute"; + elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`; + elTip.appendChild(elTipJSAttribute); + } - document.body.appendChild(elTip); + document.body.appendChild(elTip); - let elementTop = window.pageYOffset + elementRect.top; + let elementTop = window.pageYOffset + elementRect.top; - let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; - if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { - // show above when otherwise out of bounds - elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; - } else { - // show tooltip on below by default - elTip.style.top = desiredTooltipTop; - } - elTip.style.left = window.pageXOffset + elementRect.left; + if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { + // show above when otherwise out of bounds + elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight; + } else { + // show tooltip on below by default + elTip.style.top = desiredTooltipTop; + } + elTip.style.left = window.pageXOffset + elementRect.left; - this._tooltipDiv = elTip; + this._tooltipDiv = elTip; + }.bind(this), TOOLTIP_DELAY); }.bind(this), false); element.addEventListener("mouseout", function() { this._removeTooltipIfExists(); From 83051940a854d8bee91e23f6542fa45554503e98 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Oct 2018 04:49:45 +0200 Subject: [PATCH 240/276] Fix QA feedback --- .../system/assets/data/createAppTooltips.json | 45 ++++++++++++------- scripts/system/edit.js | 3 +- scripts/system/html/js/createAppTooltip.js | 26 ++++++++++- scripts/system/html/js/entityProperties.js | 2 + 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 7cc9e0a97e..0e6818c512 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -147,7 +147,7 @@ "tooltip": "The speed of the animation." }, "textures": { - "tooltip": "The URL of a JPG or PNG image file to display for each particle." + "tooltip": "A JSON string containing a texture. Use a name from the Original Texture property to override it." }, "originalTextures": { "tooltip": "A JSON string containing the original texture used on the model." @@ -171,6 +171,10 @@ "maxParticles": { "tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones." }, + "particleTextures": { + "tooltip": "The URL of a JPG or PNG image file to display for each particle.", + "jsPropertyName": "textures" + }, "emitRate": { "tooltip": "The number of particles per second to emit." }, @@ -244,16 +248,16 @@ "rotateWithEntity": { "tooltip": "If enabled, each particle will spin relative to the roation of the entity as a whole." }, - "azimuthStart": { + "polarStart": { "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." }, - "azimuthFinish": { + "polarFinish": { "tooltip": "" }, - "polarStart": { + "azimuthStart": { "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." }, - "polarFinish": { + "azimuthFinish": { "tooltip": "" }, "lightColor": { @@ -300,6 +304,12 @@ "registrationPoint": { "tooltip": "The point in the entity at which the entity is rotated about." }, + "visible": { + "tooltip": "If enabled, this entity will be visible." + }, + "locked": { + "tooltip": "If enabled, this entity will be locked." + }, "collisionless": { "tooltip": "If enabled, this entity will collide with other entities or avatars." }, @@ -318,20 +328,22 @@ "tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).", "jsPropertyName": "collidesWith" }, - "collidesWithOtherAvatars": { + "collidesWithOtherAvatar": { "tooltip": "If enabled, this entity will collide with other user's avatars.", "jsPropertyName": "collidesWith" }, + "collidesWithMyAvatar": { + "tooltip": "If enabled, this entity will collide with your own avatar.", + "jsPropertyName": "collidesWith" + }, "collisionSoundURL": { "tooltip": "The URL of a sound to play when the entity collides with something else." }, - "grabbable": { - "tooltip": "If enabled, this entity will allow grabbing input and will be moveable.", - "jsPropertyName": "userData[\"grabbableKey\"][\"grabbable\"]" + "grab.grabbable": { + "tooltip": "If enabled, this entity will allow grabbing input and will be moveable." }, - "triggerable": { - "tooltip": "If enabled, the collider on this entity is used for triggering events.", - "jsPropertyName": "userData[\"grabbableKey\"][\"triggerable\"]" + "grab.triggerable": { + "tooltip": "If enabled, the collider on this entity is used for triggering events." }, "cloneable": { "tooltip": "If enabled, this entity can be duplicated." @@ -348,9 +360,8 @@ "cloneAvatarEntity": { "tooltip": "If enabled, then clones created from this entity will be created as avatar entities." }, - "ignoreIK": { - "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand.", - "jsPropertyName": "userData[\"grabbableKey\"][\"ignoreIK\"]" + "grab.grabFollowsController": { + "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand." }, "canCastShadow": { "tooltip": "If enabled, this geometry of this entity casts shadows when a shadow-casting light source shines on it." @@ -411,6 +422,10 @@ "acceleration": { "tooltip": "A acceleration that the entity should move with, in world space." }, + "alignToGrid": { + "tooltip": "Used to align entities to the grid, or floor of the environment.", + "skipJSProperty": true + }, "createModel": { "tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.", "skipJSProperty": true diff --git a/scripts/system/edit.js b/scripts/system/edit.js index c6432ac2f9..6425806771 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2484,7 +2484,8 @@ var PropertiesTool = function (opts) { } else if (data.type === "tooltipsRequest") { emitScriptEvent({ type: 'tooltipsReply', - tooltips: Script.require('./assets/data/createAppTooltips.json') + tooltips: Script.require('./assets/data/createAppTooltips.json'), + hmdActive: HMD.active, }); } }; diff --git a/scripts/system/html/js/createAppTooltip.js b/scripts/system/html/js/createAppTooltip.js index edd0f6366a..a42e5efe05 100644 --- a/scripts/system/html/js/createAppTooltip.js +++ b/scripts/system/html/js/createAppTooltip.js @@ -8,17 +8,20 @@ const CREATE_APP_TOOLTIP_OFFSET = 20; const TOOLTIP_DELAY = 500; // ms +const TOOLTIP_DEBUG = false; function CreateAppTooltip() { this._tooltipData = null; this._tooltipDiv = null; this._delayTimeout = null; + this._isEnabled = false; } CreateAppTooltip.prototype = { _tooltipData: null, _tooltipDiv: null, _delayTimeout: null, + _isEnabled: null, _removeTooltipIfExists: function() { if (this._delayTimeout !== null) { @@ -32,12 +35,19 @@ CreateAppTooltip.prototype = { } }, + setIsEnabled: function(isEnabled) { + this._isEnabled = isEnabled; + }, + setTooltipData: function(tooltipData) { this._tooltipData = tooltipData; }, registerTooltipElement: function(element, tooltipID) { element.addEventListener("mouseover", function() { + if (!this._isEnabled) { + return; + } this._removeTooltipIfExists(); @@ -45,7 +55,10 @@ CreateAppTooltip.prototype = { let tooltipData = this._tooltipData[tooltipID]; if (!tooltipData || tooltipData.tooltip === "") { - return; + if (!TOOLTIP_DEBUG) { + return; + } + tooltipData = {tooltip: 'PLEASE SET THIS TOOLTIP'}; } let elementRect = element.getBoundingClientRect(); @@ -74,6 +87,7 @@ CreateAppTooltip.prototype = { let elementTop = window.pageYOffset + elementRect.top; let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET; + let desiredTooltipLeft = window.pageXOffset + elementRect.left; if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) { // show above when otherwise out of bounds @@ -82,12 +96,20 @@ CreateAppTooltip.prototype = { // show tooltip on below by default elTip.style.top = desiredTooltipTop; } - elTip.style.left = window.pageXOffset + elementRect.left; + if ((window.innerWidth + window.pageXOffset) < (desiredTooltipLeft + elTip.clientWidth)) { + elTip.style.left = document.body.clientWidth + window.pageXOffset - elTip.offsetWidth; + } else { + elTip.style.left = desiredTooltipLeft; + } this._tooltipDiv = elTip; }.bind(this), TOOLTIP_DELAY); }.bind(this), false); element.addEventListener("mouseout", function() { + if (!this._isEnabled) { + return; + } + this._removeTooltipIfExists(); }.bind(this), false); } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 59c2ba702b..173d806d59 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -3154,6 +3154,7 @@ function loaded() { } } } else if (data.type === 'tooltipsReply') { + createAppTooltip.setIsEnabled(!data.hmdActive); createAppTooltip.setTooltipData(data.tooltips); } }); @@ -3170,6 +3171,7 @@ function loaded() { let elLabel = document.createElement('label'); elLabel.setAttribute("for", serverScriptStatusElementID); elLabel.innerText = "Server Script Status"; + createAppTooltip.registerTooltipElement(elLabel, "serverScriptsStatus"); let elServerScriptStatus = document.createElement('span'); elServerScriptStatus.setAttribute("id", serverScriptStatusElementID); elDiv.appendChild(elLabel); From bb7d1f8afb2cef4a47a7b21ebc75f60742235161 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 23 Oct 2018 22:37:21 +0200 Subject: [PATCH 241/276] fix broken and missing tooltips --- .../system/assets/data/createAppTooltips.json | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 0e6818c512..83ddcaa34b 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -5,9 +5,6 @@ "color": { "tooltip": "The RGB value of this entity." }, - "materialURL": { - "tooltip": "The URL of a JSON file containing the material. Use this to change the entity's look. " - }, "text": { "tooltip": "The text to display on the entity." }, @@ -35,7 +32,7 @@ "keyLightMode": { "tooltip": "Configures the key light in the zone. This light is directional." }, - "keyLightColor": { + "keyLight.color": { "tooltip": "The color of the key light." }, "keyLight.intensity": { @@ -152,7 +149,7 @@ "originalTextures": { "tooltip": "A JSON string containing the original texture used on the model." }, - "imageUrl": { + "image": { "tooltip": "The URL for the image source.", "jsPropertyName": "textures" }, @@ -279,6 +276,36 @@ "cutoff": { "tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam." }, + "materialURL": { + "tooltip": "The URL to an external JSON file or \"materialData\", \"materialData? to use Material Data." + }, + "materialData": { + "tooltip": "Can be used instead of a JSON file when material set to materialData." + }, + "materialNameToReplace": { + "tooltip": "Material name of parent entity to map this material entity on.", + "jsPropertyName": "parentMaterialName" + }, + "submeshToReplace": { + "tooltip": "Submesh index of the parent entity to map this material on.", + "jsPropertyName": "parentMaterialName" + }, + "selectSubmesh": { + "tooltip": "If enabled, \"Select Submesh\" property will show up, otherwise \"Material Name to Replace\" will be shown.", + "skipJSProperty": true + }, + "priority": { + "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." + }, + "materialMappingPos": { + "tooltip": "The offset position of the bottom left of the material within the parent's UV space." + }, + "materialMappingScale": { + "tooltip": "How many times the material will repeat in each direction within the parent's UV space." + }, + "materialMappingRot": { + "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." + }, "id": { "tooltip": "The unique identifier of this entity." }, From 13cad17793d8d5d2751ac3b8dae83d290838a7b3 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 24 Oct 2018 21:35:47 +0200 Subject: [PATCH 242/276] better style js attributes --- scripts/system/html/css/edit-style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 4ad886f1b7..cf7124d9eb 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1614,7 +1614,8 @@ input.rename-entity { } .createAppTooltip .createAppTooltipJSAttribute { - font-size: 10px; + font-family: Raleway-SemiBold; + font-size: 11px; color: #000000; bottom: 0; margin-top: 5px; From 252e504ab91e9e53bc963f386c4ee122e205343d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 20 Oct 2018 10:12:52 -0700 Subject: [PATCH 243/276] attempt to keep userData and new grab properties in sync --- .../entities/src/EntityItemProperties.cpp | 10 + libraries/entities/src/EntityItemProperties.h | 1 + .../entities/src/EntityScriptingInterface.cpp | 232 ++++++++++++++++++ libraries/entities/src/EntityTree.cpp | 211 ++++++++-------- libraries/entities/src/EntityTree.h | 2 + libraries/entities/src/GrabPropertyGroup.cpp | 30 +++ 6 files changed, 388 insertions(+), 98 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 37d8dc0bdc..d901592759 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -3803,6 +3803,16 @@ bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const { return parentRelatedPropertyChanged() || dimensionsChanged(); } +bool EntityItemProperties::grabbingRelatedPropertyChanged() const { + const GrabPropertyGroup& grabProperties = getGrab(); + return grabProperties.triggerableChanged() || grabProperties.grabbableChanged() || + grabProperties.grabFollowsControllerChanged() || grabProperties.grabKinematicChanged() || + grabProperties.equippableChanged() || grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || grabProperties.equippableLeftRotationChanged() || + grabProperties.equippableRightRotationChanged() || grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || grabProperties.equippableIndicatorOffsetChanged(); +} + // Checking Certifiable Properties #define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N() #define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString() diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index ae2c402d22..c91ccda5aa 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -108,6 +108,7 @@ public: bool getScalesWithParent() const; bool parentRelatedPropertyChanged() const; bool queryAACubeRelatedPropertyChanged() const; + bool grabbingRelatedPropertyChanged() const; AABox getAABox() const; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index f8b22fdbae..ae951c7862 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -16,6 +16,9 @@ #include #include +#include +#include +#include #include #include @@ -37,6 +40,7 @@ #include "WebEntityItem.h" #include #include +#include "GrabPropertyGroup.h" const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}"; const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}"; @@ -237,6 +241,229 @@ EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProper } +void synchronizeSpatialKey(const GrabPropertyGroup& grabProperties, QJsonObject& grabbableKey, bool& userDataChanged) { + if (grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged() || + grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || + grabProperties.equippableIndicatorOffsetChanged()) { + + QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); + + if (grabProperties.equippableLeftPositionChanged()) { + if (grabProperties.getEquippableLeftPosition() == INITIAL_LEFT_EQUIPPABLE_POSITION) { + spatialKey.remove("leftRelativePosition"); + } else { + spatialKey["leftRelativePosition"] = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition())); + } + } + if (grabProperties.equippableRightPositionChanged()) { + if (grabProperties.getEquippableRightPosition() == INITIAL_RIGHT_EQUIPPABLE_POSITION) { + spatialKey.remove("rightRelativePosition"); + } else { + spatialKey["rightRelativePosition"] = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition())); + } + } + if (grabProperties.equippableLeftRotationChanged()) { + spatialKey["relativeRotation"] = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation())); + } else if (grabProperties.equippableRightRotationChanged()) { + spatialKey["relativeRotation"] = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())); + } + + grabbableKey["spatialKey"] = spatialKey; + userDataChanged = true; + } +} + + +void synchronizeGrabbableKey(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { + if (grabProperties.triggerableChanged() || + grabProperties.grabbableChanged() || + grabProperties.grabFollowsControllerChanged() || + grabProperties.grabKinematicChanged() || + grabProperties.equippableChanged()) { + + QJsonObject grabbableKey = userData["grabbableKey"].toObject(); + + if (grabProperties.triggerableChanged()) { + if (grabProperties.getTriggerable()) { + grabbableKey["triggerable"] = true; + } else { + grabbableKey.remove("triggerable"); + } + } + if (grabProperties.grabbableChanged()) { + if (grabProperties.getGrabbable()) { + grabbableKey.remove("grabbable"); + } else { + grabbableKey["grabbable"] = false; + } + } + if (grabProperties.grabFollowsControllerChanged()) { + if (grabProperties.getGrabFollowsController()) { + grabbableKey.remove("ignoreIK"); + } else { + grabbableKey["ignoreIK"] = false; + } + } + if (grabProperties.grabKinematicChanged()) { + if (grabProperties.getGrabKinematic()) { + grabbableKey.remove("kinematic"); + } else { + grabbableKey["kinematic"] = false; + } + } + if (grabProperties.equippableChanged()) { + if (grabProperties.getEquippable()) { + grabbableKey["equippable"] = true; + } else { + grabbableKey.remove("equippable"); + } + } + + if (grabbableKey.contains("spatialKey")) { + synchronizeSpatialKey(grabProperties, grabbableKey, userDataChanged); + } + + userData["grabbableKey"] = grabbableKey; + userDataChanged = true; + } +} + +void synchronizeGrabJoints(const GrabPropertyGroup& grabProperties, QJsonObject& joints) { + QJsonArray rightHand = joints["RightHand"].toArray(); + QJsonObject rightHandPosition = rightHand.size() > 0 ? rightHand[0].toObject() : QJsonObject(); + QJsonObject rightHandRotation = rightHand.size() > 1 ? rightHand[1].toObject() : QJsonObject(); + QJsonArray leftHand = joints["LeftHand"].toArray(); + QJsonObject leftHandPosition = leftHand.size() > 0 ? leftHand[0].toObject() : QJsonObject(); + QJsonObject leftHandRotation = leftHand.size() > 1 ? leftHand[1].toObject() : QJsonObject(); + + if (grabProperties.equippableLeftPositionChanged()) { + leftHandPosition = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition())).toObject(); + } + if (grabProperties.equippableRightPositionChanged()) { + rightHandPosition = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition())).toObject(); + } + if (grabProperties.equippableLeftRotationChanged()) { + leftHandRotation = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation())).toObject(); + } + if (grabProperties.equippableRightRotationChanged()) { + rightHandRotation = + QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())).toObject(); + } + + rightHand[0] = rightHandPosition; + rightHand[1] = rightHandRotation; + joints["RightHand"] = rightHand; + leftHand[0] = leftHandPosition; + leftHand[1] = leftHandRotation; + joints["LeftHand"] = leftHand; +} + +void synchronizeEquipHotspot(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { + if (grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged() || + grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || + grabProperties.equippableIndicatorOffsetChanged()) { + + QJsonArray equipHotspots = userData["equipHotspots"].toArray(); + QJsonObject equipHotspot = equipHotspots[0].toObject(); + QJsonObject joints = equipHotspot["joints"].toObject(); + + synchronizeGrabJoints(grabProperties, joints); + + if (grabProperties.equippableIndicatorURLChanged()) { + equipHotspot["modelURL"] = grabProperties.getEquippableIndicatorURL(); + } + if (grabProperties.equippableIndicatorScaleChanged()) { + QJsonObject scale = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableIndicatorScale())).toObject(); + equipHotspot["radius"] = scale; + equipHotspot["modelScale"] = scale; + + } + if (grabProperties.equippableIndicatorOffsetChanged()) { + equipHotspot["position"] = + QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableIndicatorOffset())).toObject(); + } + + equipHotspot["joints"] = joints; + equipHotspots[0] = equipHotspot; + userData["equipHotspots"] = equipHotspots; + userDataChanged = true; + } +} + +void synchronizeWearable(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) { + if (grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged() || + grabProperties.equippableIndicatorURLChanged() || + grabProperties.equippableIndicatorScaleChanged() || + grabProperties.equippableIndicatorOffsetChanged()) { + + QJsonObject wearable = userData["wearable"].toObject(); + QJsonObject joints = wearable["joints"].toObject(); + + synchronizeGrabJoints(grabProperties, joints); + + wearable["joints"] = joints; + userData["wearable"] = wearable; + userDataChanged = true; + } +} + +void synchronizeEditedGrabProperties(EntityItemProperties& properties, const QString& previousUserdata) { + // After sufficient warning to content creators, we should be able to remove this. + + if (properties.grabbingRelatedPropertyChanged()) { + // This edit touches a new-style grab property, so make userData agree... + GrabPropertyGroup& grabProperties = properties.getGrab(); + + bool userDataChanged { false }; + + // if the edit changed userData, use the updated version coming along with the edit. If not, use + // what was already in the entity. + QByteArray userDataString; + if (properties.userDataChanged()) { + userDataString = properties.getUserData().toUtf8(); + } else { + userDataString = previousUserdata.toUtf8();; + } + QJsonObject userData = QJsonDocument::fromJson(userDataString).object(); + + if (userData.contains("grabbableKey")) { + synchronizeGrabbableKey(grabProperties, userData, userDataChanged); + } + if (userData.contains("equipHotspots")) { + synchronizeEquipHotspot(grabProperties, userData, userDataChanged); + } + if (userData.contains("wearable")) { + synchronizeWearable(grabProperties, userData, userDataChanged); + } + + if (userDataChanged) { + properties.setUserData(QJsonDocument(userData).toJson()); + } + + } else if (properties.userDataChanged()) { + // This edit touches userData (and doesn't touch a new-style grab property). Check for grabbableKey in the + // userdata and make the new-style grab properties agree + convertGrabUserDataToProperties(properties); + } +} + + QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) { PROFILE_RANGE(script_entities, __FUNCTION__); @@ -257,6 +484,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); + synchronizeEditedGrabProperties(propertiesWithSimID, QString()); EntityItemID id; // If we have a local entity tree set, then also update it. @@ -559,6 +787,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& simulationOwner = entity->getSimulationOwner(); }); + QString previousUserdata; if (entity) { if (properties.hasSimulationRestrictedChanges()) { if (_bidOnSimulationOwnership) { @@ -597,6 +826,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // make sure the properties has a type, so that the encode can know which properties to include properties.setType(entity->getType()); + + previousUserdata = entity->getUserData(); } else if (_bidOnSimulationOwnership) { // bail when simulation participants don't know about entity return QUuid(); @@ -605,6 +836,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& // How to check for this cheaply? properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent()); + synchronizeEditedGrabProperties(properties, previousUserdata); properties.setLastEditedBy(sessionID); // done reading and modifying properties --> start write diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0b3b8abba2..fdd92eb11c 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2493,6 +2493,118 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer return true; } +void convertGrabUserDataToProperties(EntityItemProperties& properties) { + GrabPropertyGroup& grabProperties = properties.getGrab(); + QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object(); + + QJsonValue grabbableKeyValue = userData["grabbableKey"]; + if (grabbableKeyValue.isObject()) { + QJsonObject grabbableKey = grabbableKeyValue.toObject(); + + QJsonValue wantsTrigger = grabbableKey["wantsTrigger"]; + if (wantsTrigger.isBool()) { + grabProperties.setTriggerable(wantsTrigger.toBool()); + } + QJsonValue triggerable = grabbableKey["triggerable"]; + if (triggerable.isBool()) { + grabProperties.setTriggerable(triggerable.toBool()); + } + QJsonValue grabbable = grabbableKey["grabbable"]; + if (grabbable.isBool()) { + grabProperties.setGrabbable(grabbable.toBool()); + } + QJsonValue ignoreIK = grabbableKey["ignoreIK"]; + if (ignoreIK.isBool()) { + grabProperties.setGrabFollowsController(ignoreIK.toBool()); + } + QJsonValue kinematic = grabbableKey["kinematic"]; + if (kinematic.isBool()) { + grabProperties.setGrabKinematic(kinematic.toBool()); + } + QJsonValue equippable = grabbableKey["equippable"]; + if (equippable.isBool()) { + grabProperties.setEquippable(equippable.toBool()); + } + + if (grabbableKey["spatialKey"].isObject()) { + QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); + grabProperties.setEquippable(true); + if (spatialKey["leftRelativePosition"].isObject()) { + grabProperties.setEquippableLeftPosition(qMapToVec3(spatialKey["leftRelativePosition"].toVariant())); + } + if (spatialKey["rightRelativePosition"].isObject()) { + grabProperties.setEquippableRightPosition(qMapToVec3(spatialKey["rightRelativePosition"].toVariant())); + } + if (spatialKey["relativeRotation"].isObject()) { + grabProperties.setEquippableLeftRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); + grabProperties.setEquippableRightRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); + } + } + } + + QJsonValue wearableValue = userData["wearable"]; + if (wearableValue.isObject()) { + QJsonObject wearable = wearableValue.toObject(); + QJsonObject joints = wearable["joints"].toObject(); + if (joints["LeftHand"].isArray()) { + QJsonArray leftHand = joints["LeftHand"].toArray(); + if (leftHand.size() == 2) { + grabProperties.setEquippable(true); + grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); + grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); + } + } + if (joints["RightHand"].isArray()) { + QJsonArray rightHand = joints["RightHand"].toArray(); + if (rightHand.size() == 2) { + grabProperties.setEquippable(true); + grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); + grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); + } + } + } + + QJsonValue equipHotspotsValue = userData["equipHotspots"]; + if (equipHotspotsValue.isArray()) { + QJsonArray equipHotspots = equipHotspotsValue.toArray(); + if (equipHotspots.size() > 0) { + // just take the first one + QJsonObject firstHotSpot = equipHotspots[0].toObject(); + QJsonObject joints = firstHotSpot["joints"].toObject(); + if (joints["LeftHand"].isArray()) { + QJsonArray leftHand = joints["LeftHand"].toArray(); + if (leftHand.size() == 2) { + grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); + grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); + } + } + if (joints["RightHand"].isArray()) { + QJsonArray rightHand = joints["RightHand"].toArray(); + if (rightHand.size() == 2) { + grabProperties.setEquippable(true); + grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); + grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); + } + } + QJsonValue indicatorURL = firstHotSpot["modelURL"]; + if (indicatorURL.isString()) { + grabProperties.setEquippableIndicatorURL(indicatorURL.toString()); + } + QJsonValue indicatorScale = firstHotSpot["modelScale"]; + if (indicatorScale.isDouble()) { + grabProperties.setEquippableIndicatorScale(glm::vec3((float)indicatorScale.toDouble())); + } else if (indicatorScale.isObject()) { + grabProperties.setEquippableIndicatorScale(qMapToVec3(indicatorScale.toVariant())); + } + QJsonValue indicatorOffset = firstHotSpot["position"]; + if (indicatorOffset.isObject()) { + grabProperties.setEquippableIndicatorOffset(qMapToVec3(indicatorOffset.toVariant())); + } + } + } +} + + bool EntityTree::readFromMap(QVariantMap& map) { // These are needed to deal with older content (before adding inheritance modes) int contentVersion = map["Version"].toInt(); @@ -2639,104 +2751,7 @@ bool EntityTree::readFromMap(QVariantMap& map) { // convert old grab-related userData to new grab properties if (contentVersion < (int)EntityVersion::GrabProperties) { - QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object(); - QJsonObject grabbableKey = userData["grabbableKey"].toObject(); - QJsonValue wantsTrigger = grabbableKey["wantsTrigger"]; - - GrabPropertyGroup& grabProperties = properties.getGrab(); - - if (wantsTrigger.isBool()) { - grabProperties.setTriggerable(wantsTrigger.toBool()); - } - QJsonValue triggerable = grabbableKey["triggerable"]; - if (triggerable.isBool()) { - grabProperties.setTriggerable(triggerable.toBool()); - } - QJsonValue grabbable = grabbableKey["grabbable"]; - if (grabbable.isBool()) { - grabProperties.setGrabbable(grabbable.toBool()); - } - QJsonValue ignoreIK = grabbableKey["ignoreIK"]; - if (ignoreIK.isBool()) { - grabProperties.setGrabFollowsController(ignoreIK.toBool()); - } - QJsonValue kinematic = grabbableKey["kinematic"]; - if (kinematic.isBool()) { - grabProperties.setGrabKinematic(kinematic.toBool()); - } - - if (grabbableKey["spatialKey"].isObject()) { - QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); - grabProperties.setEquippable(true); - if (spatialKey["leftRelativePosition"].isObject()) { - grabProperties.setEquippableLeftPosition(qMapToVec3(spatialKey["leftRelativePosition"].toVariant())); - } - if (spatialKey["rightRelativePosition"].isObject()) { - grabProperties.setEquippableRightPosition(qMapToVec3(spatialKey["rightRelativePosition"].toVariant())); - } - if (spatialKey["relativeRotation"].isObject()) { - grabProperties.setEquippableLeftRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); - grabProperties.setEquippableRightRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant())); - } - } - - QJsonObject wearable = userData["wearable"].toObject(); - QJsonObject joints = wearable["joints"].toObject(); - if (joints["LeftHand"].isArray()) { - QJsonArray leftHand = joints["LeftHand"].toArray(); - if (leftHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); - grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); - } - } - if (joints["RightHand"].isArray()) { - QJsonArray rightHand = joints["RightHand"].toArray(); - if (rightHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); - grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); - } - } - - if (userData["equipHotspots"].isArray()) { - QJsonArray equipHotspots = userData["equipHotspots"].toArray(); - if (equipHotspots.size() > 0) { - // just take the first one - QJsonObject firstHotSpot = equipHotspots[0].toObject(); - QJsonObject joints = firstHotSpot["joints"].toObject(); - if (joints["LeftHand"].isArray()) { - QJsonArray leftHand = joints["LeftHand"].toArray(); - if (leftHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant())); - grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant())); - } - } - if (joints["RightHand"].isArray()) { - QJsonArray rightHand = joints["RightHand"].toArray(); - if (rightHand.size() == 2) { - grabProperties.setEquippable(true); - grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant())); - grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant())); - } - } - QJsonValue indicatorURL = firstHotSpot["modelURL"]; - if (indicatorURL.isString()) { - grabProperties.setEquippableIndicatorURL(indicatorURL.toString()); - } - QJsonValue indicatorScale = firstHotSpot["modelScale"]; - if (indicatorScale.isDouble()) { - grabProperties.setEquippableIndicatorScale(glm::vec3((float)indicatorScale.toDouble())); - } else if (indicatorScale.isObject()) { - grabProperties.setEquippableIndicatorScale(qMapToVec3(indicatorScale.toVariant())); - } - QJsonValue indicatorOffset = firstHotSpot["position"]; - if (indicatorOffset.isObject()) { - grabProperties.setEquippableIndicatorOffset(qMapToVec3(indicatorOffset.toVariant())); - } - } - } + convertGrabUserDataToProperties(properties); } // Zero out the spread values that were fixed in version ParticleEntityFix so they behave the same as before diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 2f971b8566..634ffcc1f3 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -424,4 +424,6 @@ private: std::map _namedPaths; }; +void convertGrabUserDataToProperties(EntityItemProperties& properties); + #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/GrabPropertyGroup.cpp b/libraries/entities/src/GrabPropertyGroup.cpp index c433043e31..996eed4720 100644 --- a/libraries/entities/src/GrabPropertyGroup.cpp +++ b/libraries/entities/src/GrabPropertyGroup.cpp @@ -51,6 +51,9 @@ void GrabPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _d COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableLeftRotation, quat, setEquippableLeftRotation); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableRightPosition, vec3, setEquippableRightPosition); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableRightRotation, quat, setEquippableRightRotation); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorURL, QString, setEquippableIndicatorURL); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorScale, vec3, setEquippableIndicatorScale); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorOffset, vec3, setEquippableIndicatorOffset); } void GrabPropertyGroup::merge(const GrabPropertyGroup& other) { @@ -63,6 +66,9 @@ void GrabPropertyGroup::merge(const GrabPropertyGroup& other) { COPY_PROPERTY_IF_CHANGED(equippableLeftRotation); COPY_PROPERTY_IF_CHANGED(equippableRightPosition); COPY_PROPERTY_IF_CHANGED(equippableRightRotation); + COPY_PROPERTY_IF_CHANGED(equippableIndicatorURL); + COPY_PROPERTY_IF_CHANGED(equippableIndicatorScale); + COPY_PROPERTY_IF_CHANGED(equippableIndicatorOffset); } void GrabPropertyGroup::debugDump() const { @@ -77,6 +83,9 @@ void GrabPropertyGroup::debugDump() const { qCDebug(entities) << " _equippableLeftRotation:" << _equippableLeftRotation; qCDebug(entities) << " _equippableRightPosition:" << _equippableRightPosition; qCDebug(entities) << " _equippableRightRotation:" << _equippableRightRotation; + qCDebug(entities) << " _equippableIndicatorURL:" << _equippableIndicatorURL; + qCDebug(entities) << " _equippableIndicatorScale:" << _equippableIndicatorScale; + qCDebug(entities) << " _equippableIndicatorOffset:" << _equippableIndicatorOffset; } void GrabPropertyGroup::listChangedProperties(QList& out) { @@ -107,6 +116,15 @@ void GrabPropertyGroup::listChangedProperties(QList& out) { if (equippableRightRotationChanged()) { out << "grab-equippableRightRotation"; } + if (equippableIndicatorURLChanged()) { + out << "grab-equippableIndicatorURL"; + } + if (equippableIndicatorScaleChanged()) { + out << "grab-equippableIndicatorScale"; + } + if (equippableIndicatorOffsetChanged()) { + out << "grab-equippableIndicatorOffset"; + } } bool GrabPropertyGroup::appendToEditPacket(OctreePacketData* packetData, @@ -184,6 +202,9 @@ void GrabPropertyGroup::markAllChanged() { _equippableLeftRotationChanged = true; _equippableRightPositionChanged = true; _equippableRightRotationChanged = true; + _equippableIndicatorURLChanged = true; + _equippableIndicatorScaleChanged = true; + _equippableIndicatorOffsetChanged = true; } EntityPropertyFlags GrabPropertyGroup::getChangedProperties() const { @@ -215,6 +236,9 @@ void GrabPropertyGroup::getProperties(EntityItemProperties& properties) const { COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableLeftRotation, getEquippableLeftRotation); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableRightPosition, getEquippableRightPosition); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableRightRotation, getEquippableRightRotation); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorURL, getEquippableIndicatorURL); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorScale, getEquippableIndicatorScale); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorOffset, getEquippableIndicatorOffset); } bool GrabPropertyGroup::setProperties(const EntityItemProperties& properties) { @@ -231,6 +255,12 @@ bool GrabPropertyGroup::setProperties(const EntityItemProperties& properties) { setEquippableRightPosition); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableRightRotation, equippableRightRotation, setEquippableRightRotation); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorURL, equippableIndicatorURL, + setEquippableIndicatorURL); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorScale, equippableIndicatorScale, + setEquippableIndicatorScale); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorOffset, equippableIndicatorOffset, + setEquippableIndicatorOffset); return somethingChanged; } From 387a8ce933b2a9d45e7fb818a894a7761f368074 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 24 Oct 2018 10:21:34 -0700 Subject: [PATCH 244/276] fix QJsonArray handling --- libraries/entities/src/EntityScriptingInterface.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index ae951c7862..a920ed92ff 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -360,11 +360,13 @@ void synchronizeGrabJoints(const GrabPropertyGroup& grabProperties, QJsonObject& QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())).toObject(); } - rightHand[0] = rightHandPosition; - rightHand[1] = rightHandRotation; + rightHand = QJsonArray(); + rightHand.append(rightHandPosition); + rightHand.append(rightHandRotation); joints["RightHand"] = rightHand; - leftHand[0] = leftHandPosition; - leftHand[1] = leftHandRotation; + leftHand = QJsonArray(); + leftHand.append(leftHandPosition); + leftHand.append(leftHandRotation); joints["LeftHand"] = leftHand; } @@ -398,7 +400,8 @@ void synchronizeEquipHotspot(const GrabPropertyGroup& grabProperties, QJsonObjec } equipHotspot["joints"] = joints; - equipHotspots[0] = equipHotspot; + equipHotspots = QJsonArray(); + equipHotspots.append(equipHotspot); userData["equipHotspots"] = equipHotspots; userDataChanged = true; } From c5aafd4644be96280bffb98d4bb06f1717ab9c3f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 24 Oct 2018 12:24:06 -0700 Subject: [PATCH 245/276] fix spatialKey conversion --- libraries/entities/src/EntityScriptingInterface.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index a920ed92ff..3491688588 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -286,7 +286,10 @@ void synchronizeGrabbableKey(const GrabPropertyGroup& grabProperties, QJsonObjec grabProperties.grabbableChanged() || grabProperties.grabFollowsControllerChanged() || grabProperties.grabKinematicChanged() || - grabProperties.equippableChanged()) { + grabProperties.equippableChanged() || + grabProperties.equippableLeftPositionChanged() || + grabProperties.equippableRightPositionChanged() || + grabProperties.equippableRightRotationChanged()) { QJsonObject grabbableKey = userData["grabbableKey"].toObject(); From b93807b1a766526a16d8c2c8427e450fdb686a21 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 24 Oct 2018 12:32:38 -0700 Subject: [PATCH 246/276] remove conversion code from equipEntity.js --- .../controllerModules/equipEntity.js | 93 ++----------------- 1 file changed, 9 insertions(+), 84 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index f4d9c731b7..9a78c706a9 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -167,16 +167,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; -var alreadyWarned = {}; -function warnAboutUserData(props) { - if (alreadyWarned[props.id]) { - return; - } - print("Warning -- overriding grab properties with userData for " + props.id + " / " + props.name); - alreadyWarned[props.id] = true; -} - - (function() { var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; @@ -199,80 +189,15 @@ function warnAboutUserData(props) { var UNEQUIP_KEY = "u"; function getWearableData(props) { - if (props.grab.equippable) { - // if equippable is true, we know this was already converted from the old userData style to properties - return { - joints: { - LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ], - RightHand: [ props.grab.equippableRightPosition, props.grab.equippableRightRotation ] - }, - indicatorURL: props.grab.equippableIndicatorURL, - indicatorScale: props.grab.equippableIndicatorScale, - indicatorOffset: props.grab.equippableIndicatorOffset - }; - } else { - // check for old userData equippability. The JSON reader will convert userData to properties - // in EntityTree.cpp, but this won't catch things created from scripts or some items in - // the market. Eventually we'll remove this section. - try { - if (!props.userDataParsed) { - props.userDataParsed = JSON.parse(props.userData); - } - var userDataParsed = props.userDataParsed; - - // userData: { wearable: { joints: { LeftHand: {...}, RightHand: {...} } } } - if (userDataParsed.wearable && userDataParsed.wearable.joints) { - warnAboutUserData(props); - userDataParsed.wearable.indicatorURL = ""; - userDataParsed.wearable.indicatorScale = { x: 1, y: 1, z: 1 }; - userDataParsed.wearable.indicatorOffset = { x: 0, y: 0, z: 0 }; - return userDataParsed.wearable; - } - - // userData: { equipHotspots: { joints: { LeftHand: {...}, RightHand: {...} } } } - // https://highfidelity.atlassian.net/wiki/spaces/HOME/pages/51085337/Authoring+Equippable+Entities - if (userDataParsed.equipHotspots && - userDataParsed.equipHotspots.length > 0 && - userDataParsed.equipHotspots[0].joints) { - warnAboutUserData(props); - var hotSpot = userDataParsed.equipHotspots[0]; - - var indicatorScale = { x: hotSpot.radius, y: hotSpot.radius, z: hotSpot.radius }; - if (hotSpot.modelURL && hotSpot.modelURL !== "") { - indicatorScale = hotSpot.modelScale; - } - - return { - joints: hotSpot.joints, - indicatorURL: hotSpot.modelURL, - indicatorScale: indicatorScale, - indicatorOffset: hotSpot.position, - }; - } - - // userData:{grabbableKey:{spatialKey:{leftRelativePosition:{...},rightRelativePosition:{...}}}} - if (userDataParsed.grabbableKey && - userDataParsed.grabbableKey.spatialKey) { - warnAboutUserData(props); - var joints = {}; - joints.LeftHand = [ { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 } ]; - joints.RightHand = [ { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 } ]; - if (userDataParsed.grabbableKey.spatialKey.leftRelativePosition) { - joints.LeftHand = [userDataParsed.grabbableKey.spatialKey.leftRelativePosition, - userDataParsed.grabbableKey.spatialKey.relativeRotation]; - } - if (userDataParsed.grabbableKey.spatialKey.rightRelativePosition) { - joints.RightHand = [userDataParsed.grabbableKey.spatialKey.rightRelativePosition, - userDataParsed.grabbableKey.spatialKey.relativeRotation]; - } - return { joints: joints }; - } - - } catch (err) { - // don't spam logs - } - return null; - } + return { + joints: { + LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ], + RightHand: [ props.grab.equippableRightPosition, props.grab.equippableRightRotation ] + }, + indicatorURL: props.grab.equippableIndicatorURL, + indicatorScale: props.grab.equippableIndicatorScale, + indicatorOffset: props.grab.equippableIndicatorOffset + }; } function getAttachPointSettings() { From b8f1c966852d5dfaf3e6ca1083aabcafcf768e96 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 25 Oct 2018 07:46:46 -0700 Subject: [PATCH 247/276] rats --- .../controllerModules/equipEntity.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 9a78c706a9..2f343ff84b 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -189,15 +189,19 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var UNEQUIP_KEY = "u"; function getWearableData(props) { - return { - joints: { - LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ], - RightHand: [ props.grab.equippableRightPosition, props.grab.equippableRightRotation ] - }, - indicatorURL: props.grab.equippableIndicatorURL, - indicatorScale: props.grab.equippableIndicatorScale, - indicatorOffset: props.grab.equippableIndicatorOffset - }; + if (props.grab.equippable) { + return { + joints: { + LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ], + RightHand: [ props.grab.equippableRightPosition, props.grab.equippableRightRotation ] + }, + indicatorURL: props.grab.equippableIndicatorURL, + indicatorScale: props.grab.equippableIndicatorScale, + indicatorOffset: props.grab.equippableIndicatorOffset + }; + } else { + return null + } } function getAttachPointSettings() { From eb347c55963dbf23b6f17cf2586b02f8b60da97e Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 25 Oct 2018 11:30:18 -0700 Subject: [PATCH 248/276] remove particle explorer --- .../particle_explorer/hifi-entity-ui.js | 709 ------------------ .../particle_explorer/particle-style.css | 140 ---- .../particle_explorer/particleExplorer.html | 43 -- .../particle_explorer/particleExplorer.js | 485 ------------ .../particle_explorer/particleExplorerTool.js | 144 ---- .../particle_explorer/underscore-min.js | 6 - 6 files changed, 1527 deletions(-) delete mode 100644 scripts/system/particle_explorer/hifi-entity-ui.js delete mode 100644 scripts/system/particle_explorer/particle-style.css delete mode 100644 scripts/system/particle_explorer/particleExplorer.html delete mode 100644 scripts/system/particle_explorer/particleExplorer.js delete mode 100644 scripts/system/particle_explorer/particleExplorerTool.js delete mode 100644 scripts/system/particle_explorer/underscore-min.js diff --git a/scripts/system/particle_explorer/hifi-entity-ui.js b/scripts/system/particle_explorer/hifi-entity-ui.js deleted file mode 100644 index 62a0aadc86..0000000000 --- a/scripts/system/particle_explorer/hifi-entity-ui.js +++ /dev/null @@ -1,709 +0,0 @@ -/* global window, document, print, alert, console,setTimeout, clearTimeout, _ $ */ -/* eslint no-console: 0 */ - -/** -UI Builder V1.0 - -Created by Matti 'Menithal' Lahtinen -24/5/2017 -Copyright 2017 High Fidelity, Inc. - -This can eventually be expanded to all of Edit, for now, starting -with Particles Only. - -This is created for the sole purpose of streamliming the bridge, and to simplify -the logic between an inputfield in WebView and Entities in High Fidelity. - -We also do not need anything as heavy as jquery or any other platform, -as we are mostly only building for QT (while, all the other JS frameworks usually do alot of polyfilling) - -Available Types: - - JSONInputField - Accepts JSON input, once one presses Save, it will be propegated. - Button- A Button that listens for a custom event as defined by callback - Boolean - Creates a checkbox that the user can either check or uncheck - SliderFloat - Creates a slider (with input) that has Float values from min to max. - Default is min 0, max 1 - SliderInteger - Creates a slider (with input) that has a Integer value from min to max. - Default is min 1, max 10000 - SliderRadian - Creates a slider (with input) that has Float values in degrees, - that are converted to radians. default is min 0, max Math.PI. - Texture - Creates a Image with an url input field that points to texture. - If image cannot form, show "cannot find image" - VecQuaternion - Creates a 3D Vector field that converts to quaternions. - Checkbox exists to show quaternions instead. - Color - Create field color button, that when pressed, opens the color picker. - Vector - Create a 3D Vector field that has one to one correspondence. - -The script will use this structure to build a UI that is connected The -id fields within High Fidelity - -This should make editing, and everything related much more simpler to maintain, -and If there is any changes to either the Entities or properties of - -**/ - -var RADIANS_PER_DEGREE = Math.PI / 180; -var DEBOUNCE_TIMEOUT = 125; - -var roundFloat = function (input, round) { - round = round ? round : 1000; - var sanitizedInput; - if (typeof input === "string") { - sanitizedInput = parseFloat(input); - } else { - sanitizedInput = input; - } - return Math.round(sanitizedInput * round) / round; -}; - -function HifiEntityUI(parent) { - this.parent = parent; - - var self = this; - this.sendPackage = {}; - this.settingsUpdateLock = false; - this.webBridgeSync = function(id, val) { - if (!this.settingsUpdateLock) { - this.sendPackage[id] = val; - this.webBridgeSyncDebounce(); - } - }; - this.webBridgeSyncDebounce = _.debounce(function () { - if (self.EventBridge) { - self.submitChanges(self.sendPackage); - self.sendPackage = {}; - } - }, DEBOUNCE_TIMEOUT); -} - -HifiEntityUI.prototype = { - setOnSelect: function (callback) { - this.onSelect = callback; - }, - submitChanges: function (structure) { - var message = { - messageType: "settings_update", - updatedSettings: structure - }; - this.EventBridge.emitWebEvent(JSON.stringify(message)); - }, - setUI: function (structure) { - this.structure = structure; - }, - disableFields: function () { - var fields = document.getElementsByTagName("input"); - for (var i = 0; i < fields.length; i++) { - if (fields[i].getAttribute("type") !== "button") { - fields[i].value = ""; - } - - fields[i].setAttribute("disabled", true); - } - var textures = document.getElementsByTagName("img"); - for (i = 0; i < textures.length; i++) { - textures[i].src = ""; - } - - textures = document.getElementsByClassName("with-texture"); - for (i = 0; i < textures.length; i++) { - textures[i].classList.remove("with-textures"); - textures[i].classList.add("no-texture"); - } - - var textareas = document.getElementsByTagName("textarea"); - for (var x = 0; x < textareas.length; x++) { - textareas[x].remove(); - } - }, - getSettings: function () { - var self = this; - var json = {}; - var keys = Object.keys(self.builtRows); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var el = self.builtRows[key]; - if (el.className.indexOf("checkbox") !== -1) { - json[key] = document.getElementById(key) - .checked ? true : false; - } else if (el.className.indexOf("vector-section") !== -1) { - var vector = {}; - if (el.className.indexOf("rgb") !== -1) { - var red = document.getElementById(key + "-red"); - var blue = document.getElementById(key + "-blue"); - var green = document.getElementById(key + "-green"); - vector.red = red.value; - vector.blue = blue.value; - vector.green = green.value; - } else if (el.className.indexOf("pyr") !== -1) { - var p = document.getElementById(key + "-Pitch"); - var y = document.getElementById(key + "-Yaw"); - var r = document.getElementById(key + "-Roll"); - vector.x = p.value; - vector.y = y.value; - vector.z = r.value; - } else { - var x = document.getElementById(key + "-x"); - var ey = document.getElementById(key + "-y"); - var z = document.getElementById(key + "-z"); - vector.x = x.value; - vector.y = ey.value; - vector.z = z.value; - } - json[key] = vector; - } else if (el.className.indexOf("radian") !== -1) { - json[key] = document.getElementById(key).value * RADIANS_PER_DEGREE; - } else if (el.className.length > 0) { - json[key] = document.getElementById(key).value; - } - } - - - return json; - }, - fillFields: function (currentProperties) { - var self = this; - var fields = document.getElementsByTagName("input"); - - if (!currentProperties.locked) { - for (var i = 0; i < fields.length; i++) { - fields[i].removeAttribute("disabled"); - if (fields[i].hasAttribute("data-max")) { - // Reset Max to original max - fields[i].setAttribute("max", fields[i].getAttribute("data-max")); - } - } - } - - if (self.onSelect) { - self.onSelect(); - } - var keys = Object.keys(currentProperties); - - - for (var e in keys) { - if (keys.hasOwnProperty(e)) { - var value = keys[e]; - - var property = currentProperties[value]; - var field = self.builtRows[value]; - if (field) { - var el = document.getElementById(value); - - if (field.className.indexOf("radian") !== -1) { - el.value = property / RADIANS_PER_DEGREE; - el.onchange({ - target: el - }); - } else if (field.className.indexOf("range") !== -1 || field.className.indexOf("texture") !== -1) { - el.value = property; - el.onchange({ - target: el - }); - } else if (field.className.indexOf("checkbox") !== -1) { - if (property) { - el.setAttribute("checked", property); - } else { - el.removeAttribute("checked"); - } - } else if (field.className.indexOf("vector-section") !== -1) { - if (field.className.indexOf("rgb") !== -1) { - var red = document.getElementById(value + "-red"); - var blue = document.getElementById(value + "-blue"); - var green = document.getElementById(value + "-green"); - red.value = parseInt(property.red); - blue.value = parseInt(property.blue); - green.value = parseInt(property.green); - - red.oninput({ - target: red - }); - } else if (field.className.indexOf("xyz") !== -1) { - var x = document.getElementById(value + "-x"); - var y = document.getElementById(value + "-y"); - var z = document.getElementById(value + "-z"); - - x.value = roundFloat(property.x, 100); - y.value = roundFloat(property.y, 100); - z.value = roundFloat(property.z, 100); - } else if (field.className.indexOf("pyr") !== -1) { - var pitch = document.getElementById(value + "-Pitch"); - var yaw = document.getElementById(value + "-Yaw"); - var roll = document.getElementById(value + "-Roll"); - - pitch.value = roundFloat(property.x, 100); - yaw.value = roundFloat(property.y, 100); - roll.value = roundFloat(property.z, 100); - - } - } - } - } - } - }, - connect: function (EventBridge) { - this.EventBridge = EventBridge; - - var self = this; - - EventBridge.emitWebEvent(JSON.stringify({ - messageType: 'page_loaded' - })); - - EventBridge.scriptEventReceived.connect(function (data) { - data = JSON.parse(data); - - if (data.messageType === 'particle_settings') { - self.settingsUpdateLock = true; - self.fillFields(data.currentProperties); - self.settingsUpdateLock = false; - // Do expected property match with structure; - } else if (data.messageType === 'particle_close') { - self.disableFields(); - } - }); - }, - build: function () { - var self = this; - var sections = Object.keys(this.structure); - this.builtRows = {}; - sections.forEach(function (section, index) { - var properties = self.structure[section]; - self.addSection(self.parent, section, properties, index); - }); - }, - addSection: function (parent, section, properties, index) { - var self = this; - - var sectionDivHeader = document.createElement("fieldset"); - var title = document.createElement("legend"); - var dropDown = document.createElement("span"); - - dropDown.className = "arrow"; - sectionDivHeader.className = "major"; - title.className = "section-header"; - title.id = section + "-section"; - title.innerHTML = section; - title.appendChild(dropDown); - sectionDivHeader.appendChild(title); - - var collapsed = index !== 0; - - dropDown.innerHTML = collapsed ? "L" : "M"; - sectionDivHeader.setAttribute("collapsed", collapsed); - parent.appendChild(sectionDivHeader); - - var sectionDivBody = document.createElement("div"); - sectionDivBody.className = "property-group"; - - var animationWrapper = document.createElement("div"); - animationWrapper.className = "section-wrap"; - - for (var property in properties) { - if (properties.hasOwnProperty(property)) { - var builtRow = self.addElement(animationWrapper, properties[property]); - var id = properties[property].id; - if (id) { - self.builtRows[id] = builtRow; - } - } - } - sectionDivBody.appendChild(animationWrapper); - sectionDivHeader.appendChild(sectionDivBody); - _.defer(function () { - var height = (animationWrapper.clientHeight) + "px"; - if (collapsed) { - sectionDivBody.classList.remove("visible"); - sectionDivBody.style.maxHeight = "0px"; - } else { - sectionDivBody.classList.add("visible"); - sectionDivBody.style.maxHeight = height; - } - - title.onclick = function () { - collapsed = !collapsed; - if (collapsed) { - sectionDivBody.classList.remove("visible"); - sectionDivBody.style.maxHeight = "0px"; - } else { - sectionDivBody.classList.add("visible"); - sectionDivBody.style.maxHeight = (animationWrapper.clientHeight) + "px"; - } - // sectionDivBody.style.display = collapsed ? "none": "block"; - dropDown.innerHTML = collapsed ? "L" : "M"; - title.setAttribute("collapsed", collapsed); - }; - }); - }, - addLabel: function (parent, group) { - var label = document.createElement("label"); - label.innerHTML = group.name; - parent.appendChild(label); - if (group.unit) { - var span = document.createElement("span"); - span.innerHTML = group.unit; - span.className = "unit"; - label.appendChild(span); - } - return label; - }, - addVector: function (parent, group, labels, domArray) { - var self = this; - var inputs = labels ? labels : ["x", "y", "z"]; - domArray = domArray ? domArray : []; - parent.id = group.id; - for (var index in inputs) { - var element = document.createElement("input"); - - element.setAttribute("type", "number"); - element.className = inputs[index]; - element.id = group.id + "-" + inputs[index]; - - if (group.defaultRange) { - if (group.defaultRange.min) { - element.setAttribute("min", group.defaultRange.min); - } - if (group.defaultRange.max) { - element.setAttribute("max", group.defaultRange.max); - } - if (group.defaultRange.step) { - element.setAttribute("step", group.defaultRange.step); - } - } - if (group.oninput) { - element.oninput = group.oninput; - } else { - element.oninput = function (event) { - self.webBridgeSync(group.id, { - x: domArray[0].value, - y: domArray[1].value, - z: domArray[2].value - }); - }; - } - element.onchange = element.oninput; - domArray.push(element); - } - - this.addLabel(parent, group); - var className = ""; - for (var i = 0; i < inputs.length; i++) { - className += inputs[i].charAt(0) - .toLowerCase(); - } - parent.className += " property vector-section " + className; - - // Add Tuple and the rest - var tupleContainer = document.createElement("div"); - tupleContainer.className = "tuple"; - for (var domIndex in domArray) { - var container = domArray[domIndex]; - var div = document.createElement("div"); - var label = document.createElement("label"); - label.innerHTML = inputs[domIndex] + ":"; - label.setAttribute("for", container.id); - div.appendChild(container); - div.appendChild(label); - tupleContainer.appendChild(div); - } - parent.appendChild(tupleContainer); - }, - addVectorQuaternion: function (parent, group) { - this.addVector(parent, group, ["Pitch", "Yaw", "Roll"]); - }, - addColorPicker: function (parent, group) { - var self = this; - var $colPickContainer = $('
', { - id: group.id, - class: "color-picker" - }); - var updateColors = function (red, green, blue) { - $colPickContainer.css('background-color', "rgb(" + - red + "," + - green + "," + - blue + ")"); - }; - - var inputs = ["red", "green", "blue"]; - var domArray = []; - group.oninput = function (event) { - $colPickContainer.colpickSetColor( - { - r: domArray[0].value, - g: domArray[1].value, - b: domArray[2].value - }, - true); - }; - group.defaultRange = { - min: 0, - max: 255, - step: 1 - }; - - parent.appendChild($colPickContainer[0]); - self.addVector(parent, group, inputs, domArray); - - updateColors(domArray[0].value, domArray[1].value, domArray[2].value); - - // Could probably write a custom one for this to completely write out jquery, - // but for now, using the same as earlier. - - /* Color Picker Logic Here */ - - - $colPickContainer.colpick({ - colorScheme: (group.layoutColorScheme === undefined ? 'dark' : group.layoutColorScheme), - layout: (group.layoutType === undefined ? 'hex' : group.layoutType), - submit: (group.useSubmitButton === undefined ? true : group.useSubmitButton), - color: { - r: domArray[0].value, - g: domArray[1].value, - b: domArray[2].value - }, - onChange: function (hsb, hex, rgb, el) { - updateColors(rgb.r, rgb.g, rgb.b); - - domArray[0].value = rgb.r; - domArray[1].value = rgb.g; - domArray[2].value = rgb.b; - self.webBridgeSync(group.id, { - red: rgb.r, - green: rgb.g, - blue: rgb.b - }); - }, - onSubmit: function (hsb, hex, rgb, el) { - $(el) - .css('background-color', '#' + hex); - $(el) - .colpickHide(); - domArray[0].value = rgb.r; - domArray[1].value = rgb.g; - domArray[2].value = rgb.b; - self.webBridgeSync(group.id, { - red: rgb.r, - green: rgb.g, - blue: rgb.b - }); - } - }); - }, - addTextureField: function (parent, group) { - var self = this; - this.addLabel(parent, group); - parent.className += " property texture"; - var textureImage = document.createElement("div"); - var textureUrl = document.createElement("input"); - textureUrl.setAttribute("type", "text"); - textureUrl.id = group.id; - textureImage.className = "texture-image no-texture"; - var image = document.createElement("img"); - var imageLoad = _.debounce(function (url) { - if (url.slice(0, 5).toLowerCase() === "atp:/") { - image.src = ""; - image.style.display = "none"; - textureImage.classList.remove("with-texture"); - textureImage.classList.remove("no-texture"); - textureImage.classList.add("no-preview"); - } else if (url.length > 0) { - textureImage.classList.remove("no-texture"); - textureImage.classList.remove("no-preview"); - textureImage.classList.add("with-texture"); - image.src = url; - image.style.display = "block"; - } else { - image.src = ""; - image.style.display = "none"; - textureImage.classList.remove("with-texture"); - textureImage.classList.remove("no-preview"); - textureImage.classList.add("no-texture"); - } - }, DEBOUNCE_TIMEOUT * 2); - - textureUrl.oninput = function (event) { - // Add throttle - var url = event.target.value; - imageLoad(url); - self.webBridgeSync(group.id, url); - }; - textureUrl.onchange = textureUrl.oninput; - textureImage.appendChild(image); - parent.appendChild(textureImage); - parent.appendChild(textureUrl); - }, - addSlider: function (parent, group) { - var self = this; - this.addLabel(parent, group); - parent.className += " property range"; - var container = document.createElement("div"); - container.className = "slider-wrapper"; - var slider = document.createElement("input"); - slider.setAttribute("type", "range"); - - var inputField = document.createElement("input"); - inputField.setAttribute("type", "number"); - - container.appendChild(slider); - container.appendChild(inputField); - parent.appendChild(container); - - if (group.type === "SliderInteger") { - inputField.setAttribute("min", group.min !== undefined ? group.min : 0); - inputField.setAttribute("step", 1); - - slider.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("max", group.max !== undefined ? group.max : 10000); - slider.setAttribute("data-max", group.max !== undefined ? group.max : 10000); - slider.setAttribute("step", 1); - - inputField.oninput = function (event) { - // TODO: Remove this functionality? Alan finds it confusing - if (parseInt(event.target.value) > parseInt(slider.getAttribute("max")) && group.max !== 1) { - slider.setAttribute("max", event.target.value); - } - slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value); - }; - inputField.onchange = inputField.oninput; - slider.oninput = function (event) { - inputField.value = event.target.value; - self.webBridgeSync(group.id, inputField.value); - }; - - inputField.id = group.id; - } else if (group.type === "SliderRadian") { - slider.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("max", group.max !== undefined ? group.max : 180); - slider.setAttribute("step", 1); - parent.className += " radian"; - inputField.setAttribute("min", (group.min !== undefined ? group.min : 0)); - inputField.setAttribute("max", (group.max !== undefined ? group.max : 180)); - - inputField.oninput = function (event) { - slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE); - }; - inputField.onchange = inputField.oninput; - - inputField.id = group.id; - slider.oninput = function (event) { - if (event.target.value > 0) { - inputField.value = Math.floor(event.target.value); - } else { - inputField.value = Math.ceil(event.target.value); - } - self.webBridgeSync(group.id, inputField.value * RADIANS_PER_DEGREE); - }; - var degrees = document.createElement("label"); - degrees.innerHTML = "°"; - degrees.style.fontSize = "1.4rem"; - degrees.style.display = "inline"; - degrees.style.verticalAlign = "top"; - degrees.style.paddingLeft = "0.4rem"; - container.appendChild(degrees); - - } else { - // Must then be Float - inputField.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("step", 0.01); - - slider.setAttribute("min", group.min !== undefined ? group.min : 0); - slider.setAttribute("max", group.max !== undefined ? group.max : 1); - slider.setAttribute("data-max", group.max !== undefined ? group.max : 1); - slider.setAttribute("step", 0.01); - - inputField.oninput = function (event) { - // TODO: Remove this functionality? Alan finds it confusing - if (parseFloat(event.target.value) > parseFloat(slider.getAttribute("max")) && group.max !== 1) { - slider.setAttribute("max", event.target.value); - } - - slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value); - // bind web sock update here. - }; - inputField.onchange = inputField.oninput; - slider.oninput = function (event) { - inputField.value = event.target.value; - self.webBridgeSync(group.id, inputField.value); - }; - - inputField.id = group.id; - } - - // UpdateBinding - }, - addCheckBox: function (parent, group) { - var checkBox = document.createElement("input"); - checkBox.setAttribute("type", "checkbox"); - var self = this; - checkBox.onchange = function (event) { - self.webBridgeSync(group.id, event.target.checked); - }; - checkBox.id = group.id; - parent.appendChild(checkBox); - var label = this.addLabel(parent, group); - label.setAttribute("for", checkBox.id); - parent.className += " property checkbox"; - }, - addElement: function (parent, group) { - var self = this; - var property = document.createElement("div"); - property.id = group.id; - - var row = document.createElement("div"); - switch (group.type) { - case "Button": - var button = document.createElement("input"); - button.setAttribute("type", "button"); - button.id = group.id; - if (group.disabled) { - button.disabled = group.disabled; - } - button.className = group.class; - button.value = group.name; - - button.onclick = group.callback; - parent.appendChild(button); - break; - case "Row": - var hr = document.createElement("hr"); - hr.className = "splitter"; - if (group.id) { - hr.id = group.id; - } - parent.appendChild(hr); - break; - case "Boolean": - self.addCheckBox(row, group); - parent.appendChild(row); - break; - case "SliderFloat": - case "SliderInteger": - case "SliderRadian": - self.addSlider(row, group); - parent.appendChild(row); - break; - case "Texture": - self.addTextureField(row, group); - parent.appendChild(row); - break; - case "Color": - self.addColorPicker(row, group); - parent.appendChild(row); - break; - case "Vector": - self.addVector(row, group); - parent.appendChild(row); - break; - case "VectorQuaternion": - self.addVectorQuaternion(row, group); - parent.appendChild(row); - break; - default: - console.log("not defined"); - } - return row; - } -}; \ No newline at end of file diff --git a/scripts/system/particle_explorer/particle-style.css b/scripts/system/particle_explorer/particle-style.css deleted file mode 100644 index cde325f6c6..0000000000 --- a/scripts/system/particle_explorer/particle-style.css +++ /dev/null @@ -1,140 +0,0 @@ -/* -// particle-style.css -// -// Created by Matti 'Menithal' Lahtinen on 21 May 2017 -// Copyright 2017 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 -*/ - - -.property-group { - max-height: 0; - -webkit-transition: max-height 0.15s ease-out; - transition: max-height 0.15s ease-out; - overflow: hidden; -} -.property-group.visible { - transition: max-height 0.25s ease-in; -} -.section-wrap { - width: 100%; -} -.property { - padding: 0.4rem 0; - margin: 0; -} -.property.checkbox { - margin: 0; -} -.property.range label{ - display: block; -} - -input[type="button"] { - margin: 0.4rem; - min-width: 6rem; -} -input[type="text"] { - margin: 0; -} -.property.range input[type=number]{ - margin-left: 0.8rem; - width: 5.4rem; - height: 1.8rem; -} -input[type=range] { - -webkit-appearance: none; - background: #2e2e2e; - height: 1.8rem; - border-radius: 1rem; -} -input[type=range]::-webkit-slider-thumb { - -webkit-appearance:none; - width: 0.6rem; - height: 1.8rem; - padding:0; - margin: 0; - background-color: #696969; - border-radius: 1rem; -} -input[type=range]::-webkit-slider-thumb:hover { - background-color: white; -} -input[type=range]:focus { /*#252525*/ - outline: none; -} -.tuple label { - text-transform: capitalize; -} -.slider-wrapper { - display: table; - padding: 0.4rem 0; -} -hr.splitter{ - width: 100%; - padding: 0.2rem 0 0 0; - margin: 0; - position: relative; - clear: both; -} -hr.splitter:last-of-type{ - padding:0; -} -#rem { - height: 1rem; - width: 1rem; -} -.property { - min-height: 2rem; -} -.property.vector-section{ - - width: 24rem; -} - -.property.texture { - display: block; -} -.property.texture input{ - margin: 0.4rem 0; -} -.texture-image img{ - padding: 0; - margin: 0; - width: 100%; - height: 100%; - display: none; -} -.texture-image { - display: block; - position: relative; - background-repeat: no-repeat; - background-position: center; - background-size: 100% 100%; - margin-top: 0.4rem; - height:128px; - width: 128px; - background-image: url('data:image/png;base64,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'); -} - -.texture-image.no-texture { - background-image: url('data:image/png;base64,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'); -} - -.texture-image.no-preview { - background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAA8sSURBVHhe7Z3rbxXFG8d7B9SWthRabLmIYlHkIEXKJdXYBEXxHtEXprwxxsR3/jG+8PLCaDDGeAkmKsTEoCUVKoVCA6WNtLS2UEUKBSy0tKW/D+eZM9nu7tmz55z+mC2Zz4tl9tk5c2bnO/PMM2dnS+6nn36aYzFH7vvvv6+SFhMoAY4fPy7nljvG448/zjFPTiymsAIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjmLhQgPz+/pKRk3rx56jzaRHFf0ObNmxctWkTi7Nmzp0+fFqNm+/btRUVFP/30kzp3UFtbu27duqVLl+bl3e5Y169f7+rqam1tvXnzpmSIFNHdF1RTU7M6TkNDQ0FBgbImWLVqFZfUSQKyvfzyy88991x1dfXU1NSFCxdGRkbuueeeurq6pqam0tJSlS96RNcFSQvSo9V5IC+88MIDDzwwOjr6448/fvTRR19++eVnn322Z8+ev//+u7i4+M0331ywYIHKGjGiK8Aff/zBMRaL5ebmiiUZjz322MqVK/Ez33333ZkzZxgBYh8eHt67d++lS5do/W3btokxakRXANxIf38/3mPNmjXKlARxpkeOHKGtxaIZHx9vaWkhwfTg9WZRILoCgIQG0r7JKC8vlxm7s7NTLC6YyW/cuFFYWIiPUqYoEWkB+vr6cOJLlizBwyiTB2l9vA0xj1hcTE9PDw4OkiA6EkukiLQAcOzYMY4bN26UUy8LFy7k+O+//8qpL1euXOF43333yWmkiLoATKqEQwSmlZWVyjQTIiWOwZG+npYjSNQFwIG0tbWRqK+vF4sL1r0qlZzJyUmOYXLeeaIuAHR3d+PfmQbE27hgguUY3LgS/0RzHMwBAei/R48ezcvL8x0EOCiOxEJy6osoJ1JFjTkgAHR0dExMTBDLexe0EvsTKQUMgsWLF3OUWChqzA0BGARoQBN7wyHWa6Ojo1x6+OGHlWkmaEOoeuvWrXPnzilTlJgbAgBeiEEQi8W8Pf3kyZMct27d6v0JGsf15JNPkmA5lmyhYJY5IwAenNmYBW1RUZEyJSBMYiYoLi7etWtXWVmZsubkkHPHjh2EsCjX3NysrBFjzggANDSeRJ04wEF9//33rLYqKip27979yiuvNDY2Pvvss2+//TZ+ieBn//79V69eVbkjRv6WLVv4hxW/nEcB+iyuo6ura3x8XJnicIqToV8zGpgSlDXO2NhYZ2cnV+WnIVZtTLxEn+fPn9+3b180p9+qqiqOd9ub8ihH67M8xuPT65mf1YXocXe+KY+PGhoa6unp4Rjl1tfcbQLMOawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGyfy3oNdff72mpkadJLh27Vpvb29LS8vExIRYdu7c6dpLOz09ffPmTXLypadOnVLWnJzGxsZYLKZOPHR0dDQ3N7/33nv5+fkff/yx7/PFBQsWvPPOO5T/4YcfLly4sKmpaXBw8Ntvv5Wr7777bsAOUbINDw+Th5IpX1kTyGcPHz7c2tqqTHG4NW7wzz//9N2tHczs/BY0NjZ2PQFVLy4uXr9+/UsvvaQuJxgfH1eZ4tkKCwsrKiq2b9/u3XbozOkEzaamps6ePUueZHvcsOfl5ZFHtkH4oorzQOFU7MqVKzS0S6fy8nKxeDvckiVLOGbza2u22yW/+eYbOo46ie9Te/XVV5ctW7Z8+fK//vpLWXNyfvjhB2ctaaaGhoYNGzZs3bq1q6tLWeP88ssvdCh14oFLDz30EA3tuxFRhBGRkvHJJ5+olB8XLlxg6NCs/f39ypRo93/++Wfp0qWMP+fuCnna7N2TGp5ZngMQ48iRIyQefPBBsfhy69atgwcPjo6OlpSU+G42SQaicv80tPfBJBbslBwsQDBDQ0McpVk1CMBAx2HyFa79jUhFfeRTmTH7k7DsEky5DxBPffHiRRKytS0kNMTAwAAN4d0tigX7+fPnfaeHkEjlxbFoEIAvlTFRXV0tRhBnNTIy4hwT6TL7Asgz2zBvBUlO/K+chkQc1IoVK+RUI5YzZ87IaWZIX3buMpIJAP+Jroxv5zQgOmW52WL2BZDtyv/995+cJkMeHHJX6T42wcPgZ5gJ1HkCsWTjf4C+TCuXlpZqFyctLl6etpZpIH5F6eScAjNglgVg+n3iiSdIuHoiI/f2S19xamtrN23a9NprrzEVt7W1uSKWtWvXPu2HuhzfHkF/pFfef//9ypSTQxoLPi3lw3dV3Ez4UnU5/nicJpZuBAigvTzfyyU9DWQfAkG2UdCLL76oPeC99947f/58Et3d3cQMYhTk0b8TejGhfXt7uzpPgCfxuhf49ddfVSonp6enhyhr1apVeHyxkOYYxv8QJauUA9yaXpEQCKEH8zAJThGA1pd7lLamM0mCPNhl73vGZDsCGK10FgGffvnyZZYqP//8s7qcgCY7EUemMvz+F198ceDAAaZiyaA5duwYixov6nIcaWhpdEHSfIucBqCKm4m8hSDIBhHp3URoMgHEr9wefHoaYChw71qbjMlWgK+//pp1o/DBBx98/vnnLBfp3epyAmI4ujDs3bv3t99+I/J5/vnnfd++4/7pj17U5TjohzsuKysTL8yRNM5HwqpgVHEzce7KoYlpUynZO83qaYAOxzGbFYCQrQAsXOkXgrc7+4IYuA5WwgHvvaSEVuMoKy859vb23r6QNbQ+zof2Je2cAAQ9DYhCWU4AMPtRUBhko2B9fX1aiwAnEu3IakCOYfxPSFgN4HnwP7h7xHA6GT0NyFScZQgEZgRgimYyKCwsrKurU6Y0weHIbwO0FEfGX5bxuBPp8kR0jAPX22d8EY2Oa6qqqiJt3gVlzKFDhzjGYjFaUCzpgs/BGzQ2NnJkWg7pAMMg8Y/8Wul1Mn19fUiONtl3fzAmAP0XN8IgcM0EGzZs2JkElSOBTAMsLDiGnwBUWR74XpUjvuxiJS/TgK8AdBpUz34CAGMCgPy27hoEdC5Zr3lRORIQ8krYMzExMTAwIMaUqLI8iE/XyCCgj+NnxKLRoWf2/gcyfyBDGDNv3jw6csCP70C0QPvSUq6tzgKelK5EUxJZElazlFMX/PB6efkIJXsD0IKCgsrKSuclmpi1t6S9uBy6lJzMy1My5ae892DExdn/R8wYd+fu6DmHFcAwVgDDWAEMYwUwjBXAMFYAw1gBDGMFMIwVwDBp/xSxZs2aqqqqsbGxw4cPK1PiD2W0t7cne0K9ePHitWvXXr9+Xf4aKFRWVj7yyCMkKIfSxKgpLS1lpT4yMqIrxinGU6dOBf95OGH16tXV1dWuSmrkmbs6iTM5OXnjxo2enh7560Oap+O7MZz7AVzIF6kTPwI+m+FPEbT1+vXrN2/eXFJSokzxfXAYH330UXXuYd26dWRw/uoZi8WwgPPZukYKdO5vJI0FDdR5IL6V1KxYseL2FzvYuHFjQ0NDU1OTa7uRXFUnftTU1EieZKh8yUlPALott3T58mXSiC9GkJ/mA/aDyo1JNsjPz6fdr169OjU15SxnVqioqCgrK/NW0oXefrF///4DBw5QN2r1zDPPFBcXqxyhOXnypBTlReVITnoCyP20tLS4Gq6/v58hvGjRIudfi9HIrqnR0VG9jWfZsmXz58/nnoeGhiQt9llBVxIXFCCA3n7R3d3d0dFBY3EXRUVF4hjTAq8oRXlROZKTtgATExN9fX0DAwMyGsQ+PT0te3V8b1iMztqIpbe3l6JkNIh9VtCVpEGdlUyJPOjnI3J6Z0hDALkZbozuL63pbG6vReMSQFqcEcOACPhUZoj/kUrKPonwhcvTlTDbimeRNASQt1mkp9N5uUPn+y2Dg4M4Ge7f1eOQTR4taf+zcuVKfI6UI5sbli9f7pyfs0GaWwpnmLoqGYxswwr/dHNWSEMA7o37kfdecK+4b+luchUv5NudnS0iiEU/Rmfg5+XlBb/QEZ7gSjoh0CpPwOy1adMmQrVz58653tgJAz1MFTQT79+w8xJWACZSvobeoWN2r9MXAWSfmkb8u8v/UIjuaOk6igCkrYMrqXnqqad2JyAA3bZtG8N037593n2VKamvr1cFzaS2tlblSE5YAeQenLvPpJc57w0ng0thYaL3u0mLcGN6Bwf+p7CwkOmRfiqWixcv4rsIqLP3QmEqqRkeHqZWQK8njMH1U+233nor5FLDCcs3KcpFypckIOz2dLkHhiqrG7EAlZYmlqAb6Oksaoj65W+6iWOhG+pdU1IOGjjLQSGGF5nlD1BmTMhKCq2trXpcAkOT5RuV37Fjx1dffaWs4Whvb3f9DbvwhBoBdE8aiASr5y0O5B0j519MlVvSDt21/iooKBCPxFEVEYcGwhhmwAYgrUwiZSV9YUQeOnQI31VVVZXWe4NZEkoAqT3tyIrRibwQ6Ww4Qho6mvgTmoNG4ZZ0/EO70/cZ7+rzDojc+VTGe3VBur+3kvq/MInnCgINqD+JDLxQxqQWIDc3VzoyHYSB5uT333/HfUtDS2agCYhqWN8CpxKwyiVpI/XhmUhQJBkyQz7rrWRbWxvu3lXJZMhw0RW+A6QWQLoz9+DyoYI3hmFlzxHN+CAJp/+RAMk5SWqyjIXE/ySrJOsyjikLp+OzaiEKohxl+v+TWgCpt2+rgTfOu3TpEoENrQ/OcBP/w0RHyMGUKxYnrAbod84IyheCa/K4YH4KrqSvAK6i6urq3njjDcbu6dOnXTVUOWZCf1KX48opqweZOwNIEQVp/6PXTS7w77SyDHC9C5NeT0RBorOz0+V/5PcWL5OTk0hFkEq2EydOKKsHJlWVcoCjl8KTVVJUd1XStyjmp4MHD6qTBLt27VIpB3v27NEDZUMcSbugbrhBdeJHij9dTDyAvFQrWaMQXyLS+Pj4tWvX9PAn/kV5hgJhJXYxMgLIQDm+u3SBeZgOKJM2/YuhwJSoN+SWlJTQiJTphTZlzRlQSXBWkjUwsan6cBy+iLD9+PHjzc3Nzv22RLQqhwfEphBukx6mTH6wEEn2kOru/NPFc4gMn4hZZhcrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYawAhrECGMYKYBgrgGGsAIaxAhjGCmAYK4BhrACGsQIYxgpgGCuAYdS2FIsp7AgwSk7O/wCqCi/+JioQYgAAAABJRU5ErkJggg=='); -} - -#properties-list > fieldset { - margin-top: 0px; -} - -#main-header { - margin-bottom: 21px; -} - -.section-wrap { - padding: 21px 0px; -} \ No newline at end of file diff --git a/scripts/system/particle_explorer/particleExplorer.html b/scripts/system/particle_explorer/particleExplorer.html deleted file mode 100644 index ab4c249cc3..0000000000 --- a/scripts/system/particle_explorer/particleExplorer.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - -
-
- -
- -
-
- - - diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js deleted file mode 100644 index f1b7c8600f..0000000000 --- a/scripts/system/particle_explorer/particleExplorer.js +++ /dev/null @@ -1,485 +0,0 @@ -// -// particleExplorer.js -// -// Created by James B. Pollack @imgntn on 9/26/2015 -// Copyright 2017 High Fidelity, Inc. -// -// Reworked by Menithal on 20/5/2017 -// Reworked by Daniela Fontes and Artur Gomes (Mimicry) on 12/18/2017 -// -// Web app side of the App - contains GUI. -// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -/* global HifiEntityUI, openEventBridge, console, EventBridge, document, window */ -/* eslint no-console: 0, no-global-assign: 0 */ - -(function () { - - var root = document.getElementById("properties-list"); - - window.onload = function () { - var ui = new HifiEntityUI(root); - var textarea = document.createElement("textarea"); - var properties = ""; - var menuStructure = { - General: [ - { - type: "Row", - id: "export-import-field" - }, - { - id: "show-properties-button", - name: "Show Properties", - type: "Button", - class: "blue", - disabled: true, - callback: function (event) { - var insertZone = document.getElementById("export-import-field"); - var json = ui.getSettings(); - properties = JSON.stringify(json); - textarea.value = properties; - if (!insertZone.contains(textarea)) { - insertZone.appendChild(textarea); - insertZone.parentNode.parentNode.style.maxHeight = - insertZone.parentNode.clientHeight + "px"; - document.getElementById("export-properties-button").removeAttribute("disabled"); - textarea.onchange = function (e) { - if (e.target.value !== properties) { - document.getElementById("import-properties-button").removeAttribute("disabled"); - } - }; - textarea.oninput = textarea.onchange; - document.getElementById("show-properties-button").value = "Hide Properties"; - } else { - textarea.onchange = function () {}; - textarea.oninput = textarea.onchange; - textarea.value = ""; - textarea.remove(); - insertZone.parentNode.parentNode.style.maxHeight = - insertZone.parentNode.clientHeight + "px"; - document.getElementById("export-properties-button").setAttribute("disabled", true); - document.getElementById("import-properties-button").setAttribute("disabled", true); - document.getElementById("show-properties-button").value = "Show Properties"; - } - } - }, - { - id: "import-properties-button", - name: "Import", - type: "Button", - class: "blue", - disabled: true, - callback: function (event) { - ui.fillFields(JSON.parse(textarea.value)); - ui.submitChanges(JSON.parse(textarea.value)); - } - }, - { - id: "export-properties-button", - name: "Export", - type: "Button", - class: "red", - disabled: true, - callback: function (event) { - textarea.select(); - try { - var success = document.execCommand('copy'); - if (!success) { - throw "Not success :("; - } - } catch (e) { - print("couldnt copy field"); - } - } - }, - { - type: "Row" - }, - { - id: "isEmitting", - name: "Is Emitting", - type: "Boolean" - }, - { - type: "Row" - }, - { - id: "lifespan", - name: "Lifespan", - type: "SliderFloat", - min: 0.01, - max: 10 - }, - { - type: "Row" - }, - { - id: "maxParticles", - name: "Max Particles", - type: "SliderInteger", - min: 1, - max: 10000 - }, - { - type: "Row" - }, - { - id: "textures", - name: "Textures", - type: "Texture" - }, - { - type: "Row" - } - ], - Emit: [ - { - id: "emitRate", - name: "Emit Rate", - type: "SliderInteger", - max: 1000, - min: 1 - }, - { - type: "Row" - }, - { - id: "emitSpeed", - name: "Emit Speed", - type: "SliderFloat", - max: 5 - }, - { - id: "speedSpread", - name: "Speed Spread", - type: "SliderFloat", - max: 5 - }, - { - type: "Row" - }, - { - id: "emitDimensions", - name: "Emit Dimension", - type: "Vector", - defaultRange: { - min: 0, - step: 0.01 - } - }, - { - type: "Row" - }, - { - id: "emitOrientation", - unit: "deg", - name: "Emit Orientation", - type: "VectorQuaternion", - defaultRange: { - min: 0, - step: 0.01 - } - }, - { - type: "Row" - }, - { - id: "emitterShouldTrail", - name: "Emitter Should Trail", - type: "Boolean" - }, - { - type: "Row" - } - ], - Radius: [ - { - id: "particleRadius", - name: "Particle Radius", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - }, - { - id: "radiusSpread", - name: "Radius Spread", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - }, - { - id: "radiusStart", - name: "Radius Start", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - }, - { - id: "radiusFinish", - name: "Radius Finish", - type: "SliderFloat", - max: 4.0 - }, - { - type: "Row" - } - ], - Color: [ - { - id: "color", - name: "Color", - type: "Color", - defaultColor: { - red: 255, - green: 255, - blue: 255 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - }, - { - id: "colorSpread", - name: "Color Spread", - type: "Color", - defaultColor: { - red: 0, - green: 0, - blue: 0 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - }, - { - id: "colorStart", - name: "Color Start", - type: "Color", - defaultColor: { - red: 255, - green: 255, - blue: 255 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - }, - { - id: "colorFinish", - name: "Color Finish", - type: "Color", - defaultColor: { - red: 255, - green: 255, - blue: 255 - }, - layoutType: "hex", - layoutColorScheme: "dark", - useSubmitButton: false - }, - { - type: "Row" - } - ], - Acceleration: [ - { - id: "emitAcceleration", - name: "Emit Acceleration", - type: "Vector", - defaultRange: { - step: 0.01 - } - }, - { - type: "Row" - }, - { - id: "accelerationSpread", - name: "Acceleration Spread", - type: "Vector", - defaultRange: { - step: 0.01 - } - }, - { - type: "Row" - } - ], - Alpha: [ - { - id: "alpha", - name: "Alpha", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - }, - { - id: "alphaSpread", - name: "Alpha Spread", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - }, - { - id: "alphaStart", - name: "Alpha Start", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - }, - { - id: "alphaFinish", - name: "Alpha Finish", - type: "SliderFloat", - max: 1.0 - }, - { - type: "Row" - } - ], - Spin: [ - { - id: "particleSpin", - name: "Particle Spin", - type: "SliderRadian", - min: -360.0, - max: 360.0 - }, - { - type: "Row" - }, - { - id: "spinSpread", - name: "Spin Spread", - type: "SliderRadian", - max: 360.0 - }, - { - type: "Row" - }, - { - id: "spinStart", - name: "Spin Start", - type: "SliderRadian", - min: -360.0, - max: 360.0 - }, - { - type: "Row" - }, - { - id: "spinFinish", - name: "Spin Finish", - type: "SliderRadian", - min: -360.0, - max: 360.0 - }, - { - type: "Row" - }, - { - id: "rotateWithEntity", - name: "Rotate with Entity", - type: "Boolean" - }, - { - type: "Row" - } - ], - Polar: [ - { - id: "polarStart", - name: "Polar Start", - unit: "deg", - type: "SliderRadian" - }, - { - type: "Row" - }, - { - id: "polarFinish", - name: "Polar Finish", - unit: "deg", - type: "SliderRadian" - }, - { - type: "Row" - } - ], - Azimuth: [ - { - id: "azimuthStart", - name: "Azimuth Start", - unit: "deg", - type: "SliderRadian", - min: -180, - max: 0 - }, - { - type: "Row" - }, - { - id: "azimuthFinish", - name: "Azimuth Finish", - unit: "deg", - type: "SliderRadian" - }, - { - type: "Row" - } - ] - }; - ui.setUI(menuStructure); - ui.setOnSelect(function () { - document.getElementById("show-properties-button").removeAttribute("disabled"); - document.getElementById("export-properties-button").setAttribute("disabled", true); - document.getElementById("import-properties-button").setAttribute("disabled", true); - }); - ui.build(); - var overrideLoad = false; - if (openEventBridge === undefined) { - overrideLoad = true, - openEventBridge = function (callback) { - callback({ - emitWebEvent: function () {}, - submitChanges: function () {}, - scriptEventReceived: { - connect: function () { - - } - } - }); - }; - } - openEventBridge(function (EventBridge) { - ui.connect(EventBridge); - }); - if (overrideLoad) { - openEventBridge(); - } - }; -})(); diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js deleted file mode 100644 index a3be004329..0000000000 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ /dev/null @@ -1,144 +0,0 @@ -// -// particleExplorerTool.js -// -// Created by Eric Levin on 2/15/16 -// Copyright 2016 High Fidelity, Inc. -// Adds particleExplorer tool to the edit panel when a user selects a particle entity from the edit tool window -// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// -/* global ParticleExplorerTool */ - - -var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html'); - -ParticleExplorerTool = function(createToolsWindow) { - var that = {}; - that.activeParticleEntity = 0; - that.updatedActiveParticleProperties = {}; - - that.createWebView = function() { - that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - that.webView.setVisible = function(value) {}; - that.webView.webEventReceived.connect(that.webEventReceived); - createToolsWindow.webEventReceived.addListener(this, that.webEventReceived); - }; - - function emitScriptEvent(data) { - var messageData = JSON.stringify(data); - that.webView.emitScriptEvent(messageData); - createToolsWindow.emitScriptEvent(messageData); - } - - that.destroyWebView = function() { - if (!that.webView) { - return; - } - that.activeParticleEntity = 0; - that.updatedActiveParticleProperties = {}; - - emitScriptEvent({ - messageType: "particle_close" - }); - }; - - function sendParticleProperties(properties) { - emitScriptEvent({ - messageType: "particle_settings", - currentProperties: properties - }); - } - - function sendActiveParticleProperties() { - var properties = Entities.getEntityProperties(that.activeParticleEntity); - if (properties.emitOrientation) { - properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); - } - // Update uninitialized variables - if (isNaN(properties.alphaStart)) { - properties.alphaStart = properties.alpha; - } - if (isNaN(properties.alphaFinish)) { - properties.alphaFinish = properties.alpha; - } - if (isNaN(properties.radiusStart)) { - properties.radiusStart = properties.particleRadius; - } - if (isNaN(properties.radiusFinish)) { - properties.radiusFinish = properties.particleRadius; - } - if (isNaN(properties.colorStart.red)) { - properties.colorStart = properties.color; - } - if (isNaN(properties.colorFinish.red)) { - properties.colorFinish = properties.color; - } - if (isNaN(properties.spinStart)) { - properties.spinStart = properties.particleSpin; - } - if (isNaN(properties.spinFinish)) { - properties.spinFinish = properties.particleSpin; - } - sendParticleProperties(properties); - } - - function sendUpdatedActiveParticleProperties() { - sendParticleProperties(that.updatedActiveParticleProperties); - that.updatedActiveParticleProperties = {}; - } - - that.webEventReceived = function(message) { - var data = JSON.parse(message); - if (data.messageType === "settings_update") { - var updatedSettings = data.updatedSettings; - - var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish", "spinStart", "spinFinish"]; - var fallbackProps = ["alpha", "particleRadius", "color", "particleSpin"]; - for (var i = 0; i < optionalProps.length; i++) { - var fallbackProp = fallbackProps[Math.floor(i / 2)]; - var optionalValue = updatedSettings[optionalProps[i]]; - var fallbackValue = updatedSettings[fallbackProp]; - if (optionalValue && fallbackValue) { - delete updatedSettings[optionalProps[i]]; - } - } - - if (updatedSettings.emitOrientation) { - updatedSettings.emitOrientation = Quat.fromVec3Degrees(updatedSettings.emitOrientation); - } - - Entities.editEntity(that.activeParticleEntity, updatedSettings); - - var entityProps = Entities.getEntityProperties(that.activeParticleEntity, optionalProps); - - var needsUpdate = false; - for (var i = 0; i < optionalProps.length; i++) { - var fallbackProp = fallbackProps[Math.floor(i / 2)]; - var fallbackValue = updatedSettings[fallbackProp]; - if (fallbackValue) { - var optionalProp = optionalProps[i]; - if ((fallbackProp !== "color" && isNaN(entityProps[optionalProp])) || (fallbackProp === "color" && isNaN(entityProps[optionalProp].red))) { - that.updatedActiveParticleProperties[optionalProp] = fallbackValue; - needsUpdate = true; - } - } - } - - if (needsUpdate) { - sendUpdatedActiveParticleProperties(); - } - - } else if (data.messageType === "page_loaded") { - sendActiveParticleProperties(); - } - }; - - that.setActiveParticleEntity = function(id) { - that.activeParticleEntity = id; - sendActiveParticleProperties(); - }; - - return that; -}; diff --git a/scripts/system/particle_explorer/underscore-min.js b/scripts/system/particle_explorer/underscore-min.js deleted file mode 100644 index f01025b7bc..0000000000 --- a/scripts/system/particle_explorer/underscore-min.js +++ /dev/null @@ -1,6 +0,0 @@ -// Underscore.js 1.8.3 -// http://underscorejs.org -// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors -// Underscore may be freely distributed under the MIT license. -(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); -//# sourceMappingURL=underscore-min.map \ No newline at end of file From 7dff0155851bca5e23202a5c8c9704e73f54b6db Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 25 Oct 2018 10:09:38 -0700 Subject: [PATCH 249/276] maybe? --- libraries/render-utils/src/MeshPartPayload.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 2fe0368db2..4ebd92bb05 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -242,9 +242,13 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in #ifdef Q_OS_MAC // On mac AMD, we specifically need to have a _meshBlendshapeBuffer bound when using a deformed mesh pipeline // it cannot be null otherwise we crash in the drawcall using a deformed pipeline with a skinned only (not blendshaped) mesh - if ((_isBlendShaped || _isSkinned)) { - glm::vec4 data; - _meshBlendshapeBuffer = std::make_shared(sizeof(glm::vec4), reinterpret_cast(&data)); + if (_isBlendShaped) { + std::vector data(_meshNumVertices); + const auto blendShapeBufferSize = _meshNumVertices * sizeof(BlendshapeOffset); + _meshBlendshapeBuffer = std::make_shared(blendShapeBufferSize, reinterpret_cast(data.data()), blendShapeBufferSize); + } else if (_isSkinned) { + BlendshapeOffset data; + _meshBlendshapeBuffer = std::make_shared(sizeof(BlendshapeOffset), reinterpret_cast(&data), sizeof(BlendshapeOffset)); } #endif From 13acfb325ae55db7ffde438c5e344a6658431f60 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 25 Oct 2018 15:06:15 -0700 Subject: [PATCH 250/276] fix collides with properties, fix ordering for start/finish/spread properties --- scripts/system/html/js/entityProperties.js | 216 ++++++++++----------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d564a59a43..f73d634af9 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -710,15 +710,6 @@ const GROUPS = [ decimals: 2, propertyID: "particleRadius", }, - { - label: "Size Spread", - type: "slider", - min: 0, - max: 4, - step: 0.01, - decimals: 2, - propertyID: "radiusSpread", - }, { label: "Size Start", type: "slider", @@ -739,6 +730,15 @@ const GROUPS = [ propertyID: "radiusFinish", fallbackProperty: "particleRadius", }, + { + label: "Size Spread", + type: "slider", + min: 0, + max: 4, + step: 0.01, + decimals: 2, + propertyID: "radiusSpread", + }, ] }, { @@ -783,15 +783,6 @@ const GROUPS = [ decimals: 2, propertyID: "alpha", }, - { - label: "Alpha Spread", - type: "slider", - min: 0, - max: 1, - step: 0.01, - decimals: 2, - propertyID: "alphaSpread", - }, { label: "Alpha Start", type: "slider", @@ -812,6 +803,15 @@ const GROUPS = [ propertyID: "alphaFinish", fallbackProperty: "alpha", }, + { + label: "Alpha Spread", + type: "slider", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "alphaSpread", + }, ] }, { @@ -853,17 +853,6 @@ const GROUPS = [ unit: "deg", propertyID: "particleSpin", }, - { - label: "Spin Spread", - type: "slider", - min: 0, - max: 360, - step: 1, - decimals: 0, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "spinSpread", - }, { label: "Spin Start", type: "slider", @@ -888,6 +877,17 @@ const GROUPS = [ propertyID: "spinFinish", fallbackProperty: "particleSpin", }, + { + label: "Spin Spread", + type: "slider", + min: 0, + max: 360, + step: 1, + decimals: 0, + multiplier: DEGREES_TO_RADIANS, + unit: "deg", + propertyID: "spinSpread", + }, { label: "Rotate with Entity", type: "bool", @@ -1355,15 +1355,15 @@ function getPropertyInputElement(propertyID) { } function enableChildren(el, selector) { - var elSelectors = el.querySelectorAll(selector); - for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { elSelectors[selectorIndex].removeAttribute('disabled'); } } function disableChildren(el, selector) { - var elSelectors = el.querySelectorAll(selector); - for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + let elSelectors = el.querySelectorAll(selector); + for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { elSelectors[selectorIndex].setAttribute('disabled', 'disabled'); } } @@ -1371,7 +1371,7 @@ function disableChildren(el, selector) { function enableProperties() { enableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); enableChildren(document, ".colpick"); - var elLocked = getPropertyInputElement("locked"); + let elLocked = getPropertyInputElement("locked"); if (elLocked.checked === false) { removeStaticUserData(); @@ -1382,10 +1382,10 @@ function enableProperties() { function disableProperties() { disableChildren(document.getElementById("properties-list"), "input, textarea, checkbox, .dropdown dl, .color-picker"); disableChildren(document, ".colpick"); - for (var pickKey in colorPickers) { + for (let pickKey in colorPickers) { colorPickers[pickKey].colpickHide(); } - var elLocked = getPropertyInputElement("locked"); + let elLocked = getPropertyInputElement("locked"); if (elLocked.checked === true) { if ($('#property-userData-editor').css('display') === "block") { @@ -1526,7 +1526,7 @@ function getPropertyValue(originalPropertyName) { * PROPERTY UPDATE FUNCTIONS */ -function updateProperty(originalPropertyName, propertyValue) { +function updateProperty(originalPropertyName, propertyValue, isParticleProperty) { let propertyUpdate = {}; // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times let splitPropertyName = originalPropertyName.split('.'); @@ -1545,8 +1545,8 @@ function updateProperty(originalPropertyName, propertyValue) { propertyUpdate[originalPropertyName] = propertyValue; } // queue up particle property changes with the debounced sync to avoid - // causing particle emitting to reset each frame when updating values - if (properties[originalPropertyName].isParticleProperty) { + // causing particle emitting to reset excessively with each value change + if (isParticleProperty) { Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) { particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey]; }); @@ -1569,30 +1569,29 @@ function updateProperties(propertiesToUpdate) { })); } - -function createEmitTextPropertyUpdateFunction(propertyName) { +function createEmitTextPropertyUpdateFunction(propertyName, isParticleProperty) { return function() { - updateProperty(propertyName, this.value); + updateProperty(propertyName, this.value, isParticleProperty); }; } -function createEmitCheckedPropertyUpdateFunction(propertyName, inverse) { +function createEmitCheckedPropertyUpdateFunction(propertyName, inverse, isParticleProperty) { return function() { - updateProperty(propertyName, inverse ? !this.checked : this.checked); + updateProperty(propertyName, inverse ? !this.checked : this.checked, isParticleProperty); }; } -function createEmitNumberPropertyUpdateFunction(propertyName, multiplier) { +function createEmitNumberPropertyUpdateFunction(propertyName, multiplier, isParticleProperty) { return function() { if (multiplier === undefined) { multiplier = 1; } let value = parseFloat(this.value) * multiplier; - updateProperty(propertyName, value); + updateProperty(propertyName, value, isParticleProperty); }; } -function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier) { +function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier, isParticleProperty) { return function () { if (multiplier === undefined) { multiplier = 1; @@ -1601,11 +1600,11 @@ function createEmitVec2PropertyUpdateFunction(propertyName, elX, elY, multiplier x: elX.value * multiplier, y: elY.value * multiplier }; - updateProperty(propertyName, newValue); + updateProperty(propertyName, newValue, isParticleProperty); }; } -function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier) { +function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multiplier, isParticleProperty) { return function() { if (multiplier === undefined) { multiplier = 1; @@ -1615,26 +1614,26 @@ function createEmitVec3PropertyUpdateFunction(propertyName, elX, elY, elZ, multi y: elY.value * multiplier, z: elZ.value * multiplier }; - updateProperty(propertyName, newValue); + updateProperty(propertyName, newValue, isParticleProperty); }; } -function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue) { +function createEmitColorPropertyUpdateFunction(propertyName, elRed, elGreen, elBlue, isParticleProperty) { return function() { - emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value); + emitColorPropertyUpdate(propertyName, elRed.value, elGreen.value, elBlue.value, isParticleProperty); }; } -function emitColorPropertyUpdate(propertyName, red, green, blue) { +function emitColorPropertyUpdate(propertyName, red, green, blue, isParticleProperty) { let newValue = { red: red, green: green, blue: blue }; - updateProperty(propertyName, newValue); + updateProperty(propertyName, newValue, isParticleProperty); } -function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { +function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString, isParticleProperty) { if (subPropertyElement.checked) { if (propertyValue.indexOf(subPropertyString)) { propertyValue += subPropertyString + ','; @@ -1643,13 +1642,13 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen // We've unchecked, so remove propertyValue = propertyValue.replace(subPropertyString + ",", ""); } - updateProperty(propertyName, propertyValue); + updateProperty(propertyName, propertyValue, isParticleProperty); } -function createImageURLUpdateFunction(propertyName) { +function createImageURLUpdateFunction(propertyName, isParticleProperty) { return function () { - var newTextures = JSON.stringify({ "tex.picture": this.value }); - updateProperty(propertyName, newTextures); + let newTextures = JSON.stringify({ "tex.picture": this.value }); + updateProperty(propertyName, newTextures, isParticleProperty); }; } @@ -1672,7 +1671,7 @@ function createStringProperty(property, elProperty, elLabel) { elInput.readOnly = true; } - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); elProperty.appendChild(elLabel); elProperty.appendChild(elInput); @@ -1708,10 +1707,10 @@ function createBoolProperty(property, elProperty, elLabel) { let subPropertyOf = propertyData.subPropertyOf; if (subPropertyOf !== undefined) { elInput.addEventListener('change', function() { - updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName); + updateCheckedSubProperty(subPropertyOf, selectedEntityProperties[subPropertyOf], elInput, propertyName, property.isParticleProperty); }); } else { - elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse)); + elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(propertyName, propertyData.inverse, property.isParticleProperty)); } return elInput; @@ -1744,7 +1743,7 @@ function createNumberProperty(property, elProperty, elLabel) { elInput.value = defaultValue; } - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals)); + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(propertyName, propertyData.multiplier, propertyData.decimals, property.isParticleProperty)); elProperty.appendChild(elLabel); elProperty.appendChild(elInput); @@ -1790,7 +1789,7 @@ function createSliderProperty(property, elProperty, elLabel) { if (propertyData.multiplier !== undefined) { inputValue *= propertyData.multiplier; } - updateProperty(property.name, inputValue); + updateProperty(property.name, inputValue, property.isParticleProperty); }; elSlider.oninput = function (event) { let sliderValue = event.target.value; @@ -1806,7 +1805,7 @@ function createSliderProperty(property, elProperty, elLabel) { if (propertyData.multiplier !== undefined) { sliderValue *= propertyData.multiplier; } - updateProperty(property.name, sliderValue); + updateProperty(property.name, sliderValue, property.isParticleProperty); }; elDiv.appendChild(elLabel); @@ -1842,8 +1841,8 @@ function createVec3Property(property, elProperty, elLabel) { let elInputZ = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Z_INPUT], propertyData.min, propertyData.max, propertyData.step); - let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, - elInputZ, propertyData.multiplier); + let inputChangeFunction = createEmitVec3PropertyUpdateFunction(propertyName, elInputX, elInputY, elInputZ, + propertyData.multiplier, property.isParticleProperty); elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); elInputZ.addEventListener('change', inputChangeFunction); @@ -1875,8 +1874,8 @@ function createVec2Property(property, elProperty, elLabel) { let elInputY = createTupleNumberInput(elTuple, elementID, propertyData.subLabels[VECTOR_ELEMENTS.Y_INPUT], propertyData.min, propertyData.max, propertyData.step); - let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, - elInputY, propertyData.multiplier); + let inputChangeFunction = createEmitVec2PropertyUpdateFunction(propertyName, elInputX, elInputY, + propertyData.multiplier, property.isParticleProperty); elInputX.addEventListener('change', inputChangeFunction); elInputY.addEventListener('change', inputChangeFunction); @@ -1907,7 +1906,8 @@ function createColorProperty(property, elProperty, elLabel) { let elInputG = createTupleNumberInput(elTuple, elementID, "green", COLOR_MIN, COLOR_MAX, COLOR_STEP); let elInputB = createTupleNumberInput(elTuple, elementID, "blue", COLOR_MIN, COLOR_MAX, COLOR_STEP); - let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB); + let inputChangeFunction = createEmitColorPropertyUpdateFunction(propertyName, elInputR, elInputG, elInputB, + property.isParticleProperty); elInputR.addEventListener('change', inputChangeFunction); elInputG.addEventListener('change', inputChangeFunction); elInputB.addEventListener('change', inputChangeFunction); @@ -1963,7 +1963,7 @@ function createDropdownProperty(property, propertyID, elProperty, elLabel) { elInput.add(option); } - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); elProperty.appendChild(elLabel); elProperty.appendChild(elInput); @@ -1990,7 +1990,7 @@ function createTextareaProperty(property, elProperty, elLabel) { elInput.readOnly = true; } - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName)); + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(propertyName, property.isParticleProperty)); elProperty.appendChild(elInput); @@ -2056,9 +2056,9 @@ function createTextureProperty(property, elProperty, elLabel) { elInput.imageLoad = imageLoad; elInput.oninput = function (event) { // Add throttle - var url = event.target.value; + let url = event.target.value; imageLoad(url); - updateProperty(property.name, url) + updateProperty(property.name, url, property.isParticleProperty) }; elInput.onchange = elInput.oninput; @@ -2199,7 +2199,7 @@ function reloadServerScripts() { function copySkyboxURLToAmbientURL() { let skyboxURL = getPropertyInputElement("skybox.url").value; getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL; - updateProperty("ambientLight.ambientURL", skyboxURL); + updateProperty("ambientLight.ambientURL", skyboxURL, false); } @@ -2214,13 +2214,13 @@ function clearUserData() { showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); - updateProperty('userData', elUserData.value); + updateProperty('userData', elUserData.value, false); } function newJSONEditor() { deleteJSONEditor(); createJSONEditor(); - var data = {}; + let data = {}; setEditorJSON(data); hideUserDataTextArea(); hideNewJSONEditorButton(); @@ -2232,7 +2232,7 @@ function saveUserData() { } function setUserDataFromEditor(noUpdate) { - var json = null; + let json = null; try { json = editor.get(); } catch (e) { @@ -2241,7 +2241,7 @@ function setUserDataFromEditor(noUpdate) { if (json === null) { return; } else { - var text = editor.getText(); + let text = editor.getText(); if (noUpdate === true) { EventBridge.emitWebEvent( JSON.stringify({ @@ -2254,15 +2254,15 @@ function setUserDataFromEditor(noUpdate) { ); return; } else { - updateProperty('userData', text); + updateProperty('userData', text, false); } } } function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, removeKeys) { - var propertyUpdate = {}; - var parsedData = {}; - var keysToBeRemoved = removeKeys ? removeKeys : []; + let propertyUpdate = {}; + let parsedData = {}; + let keysToBeRemoved = removeKeys ? removeKeys : []; try { if ($('#property-userData-editor').css('height') !== "0px") { // if there is an expanded, we want to use its json. @@ -2277,14 +2277,14 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r if (!(groupName in parsedData)) { parsedData[groupName] = {}; } - var keys = Object.keys(updateKeyPair); + let keys = Object.keys(updateKeyPair); keys.forEach(function (key) { if (updateKeyPair[key] !== null && updateKeyPair[key] !== "null") { if (updateKeyPair[key] instanceof Element) { if (updateKeyPair[key].type === "checkbox") { parsedData[groupName][key] = updateKeyPair[key].checked; } else { - var val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value); + let val = isNaN(updateKeyPair[key].value) ? updateKeyPair[key].value : parseInt(updateKeyPair[key].value); parsedData[groupName][key] = val; } } else { @@ -2317,8 +2317,8 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r var editor = null; function createJSONEditor() { - var container = document.getElementById("property-userData-editor"); - var options = { + let container = document.getElementById("property-userData-editor"); + let options = { search: false, mode: 'tree', modes: ['code', 'tree'], @@ -2330,7 +2330,7 @@ function createJSONEditor() { alert('JSON editor:' + e); }, onChange: function() { - var currentJSONString = editor.getText(); + let currentJSONString = editor.getText(); if (currentJSONString === '{"":""}') { return; @@ -2431,13 +2431,13 @@ function clearMaterialData() { showMaterialDataTextArea(); showNewJSONMaterialEditorButton(); hideSaveMaterialDataButton(); - updateProperty('materialData', elMaterialData.value); + updateProperty('materialData', elMaterialData.value, false); } function newJSONMaterialEditor() { deleteJSONMaterialEditor(); createJSONMaterialEditor(); - var data = {}; + let data = {}; setMaterialEditorJSON(data); hideMaterialDataTextArea(); hideNewJSONMaterialEditorButton(); @@ -2449,7 +2449,7 @@ function saveMaterialData() { } function setMaterialDataFromEditor(noUpdate) { - var json = null; + let json = null; try { json = materialEditor.get(); } catch (e) { @@ -2458,7 +2458,7 @@ function setMaterialDataFromEditor(noUpdate) { if (json === null) { return; } else { - var text = materialEditor.getText(); + let text = materialEditor.getText(); if (noUpdate === true) { EventBridge.emitWebEvent( JSON.stringify({ @@ -2471,7 +2471,7 @@ function setMaterialDataFromEditor(noUpdate) { ); return; } else { - updateProperty('materialData', text); + updateProperty('materialData', text, false); } } } @@ -2479,8 +2479,8 @@ function setMaterialDataFromEditor(noUpdate) { var materialEditor = null; function createJSONMaterialEditor() { - var container = document.getElementById("property-materialData-editor"); - var options = { + let container = document.getElementById("property-materialData-editor"); + let options = { search: false, mode: 'tree', modes: ['code', 'tree'], @@ -2492,7 +2492,7 @@ function createJSONMaterialEditor() { alert('JSON editor:' + e); }, onChange: function() { - var currentJSONString = materialEditor.getText(); + let currentJSONString = materialEditor.getText(); if (currentJSONString === '{"":""}') { return; @@ -2582,11 +2582,11 @@ function saveJSONMaterialData(noUpdate) { } function bindAllNonJSONEditorElements() { - var inputs = $('input'); - var i; + let inputs = $('input'); + let i; for (i = 0; i < inputs.length; ++i) { - var input = inputs[i]; - var field = $(input); + let input = inputs[i]; + let field = $(input); // TODO FIXME: (JSHint) Functions declared within loops referencing // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { @@ -2649,7 +2649,7 @@ function setDropdownValue(event) { */ function setTextareaScrolling(element) { - var isScrolling = element.scrollHeight > element.offsetHeight; + let isScrolling = element.scrollHeight > element.offsetHeight; element.setAttribute("scrolling", isScrolling ? "true" : "false"); } @@ -3229,22 +3229,22 @@ function loaded() { let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); elParentMaterialNameString.addEventListener('change', function () { - updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value); + updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + this.value, false); }); elParentMaterialNameNumber.addEventListener('change', function () { - updateProperty("parentMaterialName", this.value); + updateProperty("parentMaterialName", this.value, false); }); elParentMaterialNameCheckbox.addEventListener('change', function () { if (this.checked) { - updateProperty("parentMaterialName", elParentMaterialNameNumber.value); + updateProperty("parentMaterialName", elParentMaterialNameNumber.value, false); showParentMaterialNameBox(true, elParentMaterialNameNumber, elParentMaterialNameString); } else { - updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value); + updateProperty("parentMaterialName", MATERIAL_PREFIX_STRING + elParentMaterialNameString.value, false); showParentMaterialNameBox(false, elParentMaterialNameNumber, elParentMaterialNameString); } }); - getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures')); + getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction('textures', false)); // Collapsible sections let elCollapsible = document.getElementsByClassName("section-header"); @@ -3339,12 +3339,12 @@ function loaded() { let propertyID = elDropdown.getAttribute("propertyID"); let property = properties[propertyID]; property.elInput = dt; - dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name)); + dt.addEventListener('change', createEmitTextPropertyUpdateFunction(property.name, property.isParticleProperty)); } elDropdowns = document.getElementsByTagName("select"); while (elDropdowns.length > 0) { - var el = elDropdowns[0]; + let el = elDropdowns[0]; el.parentNode.removeChild(el); elDropdowns = document.getElementsByTagName("select"); } From 95d160a1701fe1ea3b290f1fe2b090dca0838111 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Fri, 28 Sep 2018 15:59:04 -0700 Subject: [PATCH 251/276] Working on mac GL issues --- CMakeLists.txt | 2 +- interface/src/Application.cpp | 45 ++-- interface/src/main.cpp | 13 ++ interface/src/ui/ApplicationOverlay.cpp | 1 + .../src/RenderableWebEntityItem.cpp | 1 + libraries/gl/src/gl/Context.cpp | 30 +-- libraries/gl/src/gl/Context.h | 11 +- libraries/gl/src/gl/ContextQt.cpp | 52 ++++- libraries/gl/src/gl/GLHelpers.cpp | 7 +- libraries/gl/src/gl/GLWidget.cpp | 4 +- libraries/gl/src/gl/GLWidget.h | 2 +- libraries/gl/src/gl/GLWindow.cpp | 2 +- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 23 -- libraries/gl/src/gl/OffscreenGLCanvas.h | 3 - libraries/gl/src/gl/QOpenGLContextWrapper.cpp | 17 ++ libraries/gl/src/gl/QOpenGLContextWrapper.h | 5 + .../gpu-gl-common/src/gpu/gl/GLBackend.cpp | 199 ++++++++--------- .../gpu-gl-common/src/gpu/gl/GLBackend.h | 36 ++- .../qml/src/qml/impl/RenderEventHandler.cpp | 5 +- libraries/qml/src/qml/impl/TextureCache.cpp | 14 +- tests-manual/qml/qml/MacQml.qml | 77 +++++++ tests-manual/qml/src/MacQml.cpp | 78 +++++++ tests-manual/qml/src/MacQml.h | 16 ++ tests-manual/qml/src/StressWeb.cpp | 131 +++++++++++ tests-manual/qml/src/StressWeb.h | 34 +++ tests-manual/qml/src/TestCase.cpp | 25 +++ tests-manual/qml/src/TestCase.h | 23 ++ tests-manual/qml/src/main.cpp | 205 +++++------------- 28 files changed, 706 insertions(+), 355 deletions(-) create mode 100644 tests-manual/qml/qml/MacQml.qml create mode 100644 tests-manual/qml/src/MacQml.cpp create mode 100644 tests-manual/qml/src/MacQml.h create mode 100644 tests-manual/qml/src/StressWeb.cpp create mode 100644 tests-manual/qml/src/StressWeb.h create mode 100644 tests-manual/qml/src/TestCase.cpp create mode 100644 tests-manual/qml/src/TestCase.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d52a557cb1..6120e27b75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ if (ANDROID) set(GLES_OPTION ON) set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) else () - set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) + set(PLATFORM_QT_COMPONENTS WebEngine) endif () if (USE_GLES AND (NOT ANDROID)) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 58aad654db..700561325f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -52,6 +52,8 @@ #include #include +#include +#include #include #include @@ -971,9 +973,11 @@ OffscreenGLCanvas* _qmlShareContext { nullptr }; // and manually set THAT to be the shared context for the Chromium helper #if !defined(DISABLE_QML) OffscreenGLCanvas* _chromiumShareContext { nullptr }; -Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); #endif +Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); + Setting::Handle sessionRunTime{ "sessionRunTime", 0 }; const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 60.0f; @@ -1370,7 +1374,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _glWidget->setMouseTracking(true); // Make sure the window is set to the correct size by processing the pending events QCoreApplication::processEvents(); - _glWidget->createContext(); // Create the main thread context, the GPU backend initializeGL(); @@ -2727,46 +2730,58 @@ void Application::initializeGL() { _isGLInitialized = true; } + _glWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat()); + + // When loading QtWebEngineWidgets, it creates a global share context on startup. + // We have to account for this possibility by checking here for an existing + // global share context + auto globalShareContext = qt_gl_global_share_context(); + _glWidget->createContext(globalShareContext); + if (!_glWidget->makeCurrent()) { qCWarning(interfaceapp, "Unable to make window context current"); } #if !defined(DISABLE_QML) - // Build a shared canvas / context for the Chromium processes - { - // Disable signed distance field font rendering on ATI/AMD GPUs, due to - // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app - std::string vendor{ (const char*)glGetString(GL_VENDOR) }; - if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); - } + // Disable signed distance field font rendering on ATI/AMD GPUs, due to + // https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app + std::string vendor{ (const char*)glGetString(GL_VENDOR) }; + if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); + } + // Build a shared canvas / context for the Chromium processes + if (!globalShareContext) { // Chromium rendering uses some GL functions that prevent nSight from capturing // frames, so we only create the shared context if nsight is NOT active. if (!nsightActive()) { - _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext = new OffscreenGLCanvas(); _chromiumShareContext->setObjectName("ChromiumShareContext"); _chromiumShareContext->create(_glWidget->qglContext()); if (!_chromiumShareContext->makeCurrent()) { qCWarning(interfaceapp, "Unable to make chromium shared context current"); } - qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + globalShareContext = _chromiumShareContext->getContext(); + qt_gl_set_global_share_context(globalShareContext); _chromiumShareContext->doneCurrent(); // Restore the GL widget context if (!_glWidget->makeCurrent()) { qCWarning(interfaceapp, "Unable to make window context current"); } - } else { - qCWarning(interfaceapp) << "nSight detected, disabling chrome rendering"; } } #endif + if (!globalShareContext) { + globalShareContext = _glWidget->qglContext(); + qt_gl_set_global_share_context(globalShareContext); + } + // Build a shared canvas / context for the QML rendering { _qmlShareContext = new OffscreenGLCanvas(); _qmlShareContext->setObjectName("QmlShareContext"); - _qmlShareContext->create(_glWidget->qglContext()); + _qmlShareContext->create(globalShareContext); if (!_qmlShareContext->makeCurrent()) { qCWarning(interfaceapp, "Unable to make QML shared context current"); } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index d9396ae4d1..12a02ffd32 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "AddressManager.h" #include "Application.h" @@ -40,6 +41,18 @@ extern "C" { #endif int main(int argc, const char* argv[]) { + auto format = getDefaultOpenGLSurfaceFormat(); +#ifdef Q_OS_MAC + // Deal with some weirdness in the chromium context sharing on Mac. + // The primary share context needs to be 3.2, so that the Chromium will + // succeed in it's creation of it's command stub contexts. + format.setVersion(3, 2); + // This appears to resolve the issues with corrupted fonts on OSX. No + // idea why. + qputenv("QT_ENABLE_GLYPH_CACHE_WORKAROUND", "true"); + // https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg +#endif + QSurfaceFormat::setDefaultFormat(format); setupHifiApplication(BuildInfo::INTERFACE_NAME); QStringList arguments; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 108f20b2dd..2fb40c8c30 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -115,6 +115,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { batch.resetViewTransform(); batch.setResourceTexture(0, _uiTexture); geometryCache->renderUnitQuad(batch, glm::vec4(1), _qmlGeometryId); + batch.setResourceTexture(0, nullptr); } void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index bc9ac84c91..bce5225a5c 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -261,6 +261,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { DependencyManager::get()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId); batch.popProjectionJitter(); + batch.setResourceTexture(0, nullptr); } bool WebEntityRenderer::hasWebSurface() { diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index ad7e51fbd3..cd2b19beec 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -25,6 +25,7 @@ #include "GLLogging.h" #include "Config.h" #include "GLHelpers.h" +#include "QOpenGLContextWrapper.h" using namespace gl; @@ -68,8 +69,6 @@ void Context::updateSwapchainMemoryUsage(size_t prevSize, size_t newSize) { } -Context* Context::PRIMARY = nullptr; - Context::Context() {} Context::Context(QWindow* window) { @@ -97,9 +96,6 @@ void Context::release() { _context = nullptr; #endif _window = nullptr; - if (PRIMARY == this) { - PRIMARY = nullptr; - } updateSwapchainMemoryCounter(); } @@ -235,16 +231,10 @@ typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShare GLAPI PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); -void Context::create() { - if (!PRIMARY) { - PRIMARY = static_cast(qApp->property(hifi::properties::gl::PRIMARY_CONTEXT).value()); - } - - if (PRIMARY) { - _version = PRIMARY->_version; - } +void Context::create(QOpenGLContext* shareContext) { assert(0 != _hwnd); assert(0 == _hdc); auto hwnd = _hwnd; @@ -338,7 +328,10 @@ void Context::create() { contextAttribs.push_back(0); } contextAttribs.push_back(0); - auto shareHglrc = PRIMARY ? PRIMARY->_hglrc : 0; + if (!shareContext) { + shareContext = qt_gl_global_share_context(); + } + HGLRC shareHglrc = (HGLRC)QOpenGLContextWrapper::nativeContext(shareContext); _hglrc = wglCreateContextAttribsARB(_hdc, shareHglrc, &contextAttribs[0]); } @@ -346,11 +339,6 @@ void Context::create() { throw std::runtime_error("Could not create GL context"); } - if (!PRIMARY) { - PRIMARY = this; - qApp->setProperty(hifi::properties::gl::PRIMARY_CONTEXT, QVariant::fromValue((void*)PRIMARY)); - } - if (!makeCurrent()) { throw std::runtime_error("Could not make context current"); } @@ -368,7 +356,7 @@ OffscreenContext::~OffscreenContext() { _window->deleteLater(); } -void OffscreenContext::create() { +void OffscreenContext::create(QOpenGLContext* shareContext) { if (!_window) { _window = new QWindow(); _window->setFlags(Qt::MSWindowsOwnDC); @@ -379,5 +367,5 @@ void OffscreenContext::create() { qCDebug(glLogging) << "New Offscreen GLContext, window size = " << windowSize.width() << " , " << windowSize.height(); QGuiApplication::processEvents(); } - Parent::create(); + Parent::create(shareContext); } diff --git a/libraries/gl/src/gl/Context.h b/libraries/gl/src/gl/Context.h index b6160cbd6c..05cb361725 100644 --- a/libraries/gl/src/gl/Context.h +++ b/libraries/gl/src/gl/Context.h @@ -21,6 +21,7 @@ class QSurface; class QWindow; class QOpenGLContext; class QThread; +class QOpenGLDebugMessage; #if defined(Q_OS_WIN) #define GL_CUSTOM_CONTEXT @@ -30,7 +31,6 @@ namespace gl { class Context { protected: QWindow* _window { nullptr }; - static Context* PRIMARY; static void destroyContext(QOpenGLContext* context); #if defined(GL_CUSTOM_CONTEXT) uint32_t _version { 0x0401 }; @@ -48,6 +48,9 @@ namespace gl { public: static bool enableDebugLogger(); + static void debugMessageHandler(const QOpenGLDebugMessage &debugMessage); + static void setupDebugLogging(QOpenGLContext* context); + Context(); Context(QWindow* window); void release(); @@ -59,14 +62,14 @@ namespace gl { static void makeCurrent(QOpenGLContext* context, QSurface* surface); void swapBuffers(); void doneCurrent(); - virtual void create(); + virtual void create(QOpenGLContext* shareContext = nullptr); QOpenGLContext* qglContext(); void moveToThread(QThread* thread); static size_t evalSurfaceMemoryUsage(uint32_t width, uint32_t height, uint32_t pixelSize); static size_t getSwapchainMemoryUsage(); static void updateSwapchainMemoryUsage(size_t prevSize, size_t newSize); - + private: static std::atomic _totalSwapchainMemoryUsage; @@ -81,7 +84,7 @@ namespace gl { QWindow* _window { nullptr }; public: virtual ~OffscreenContext(); - void create() override; + void create(QOpenGLContext* shareContext = nullptr) override; }; } diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index dd65c3076c..f554877b2a 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -17,6 +17,8 @@ #include #endif +#include + #include "GLHelpers.h" using namespace gl; @@ -47,6 +49,32 @@ void Context::moveToThread(QThread* thread) { qglContext()->moveToThread(thread); } +void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) { + auto severity = debugMessage.severity(); + switch (severity) { + case QOpenGLDebugMessage::NotificationSeverity: + case QOpenGLDebugMessage::LowSeverity: + return; + default: + break; + } + qDebug(glLogging) << debugMessage; + return; +} + +void Context::setupDebugLogging(QOpenGLContext *context) { + QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(context); + QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, nullptr, [](const QOpenGLDebugMessage& message){ + Context::debugMessageHandler(message); + }); + if (logger->initialize()) { + logger->enableMessages(); + logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); + } else { + qCWarning(glLogging) << "OpenGL context does not support debugging"; + } +} + #ifndef GL_CUSTOM_CONTEXT bool Context::makeCurrent() { updateSwapchainMemoryCounter(); @@ -65,21 +93,29 @@ void Context::doneCurrent() { } } +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); const QSurfaceFormat& getDefaultOpenGLSurfaceFormat(); - -void Context::create() { +void Context::create(QOpenGLContext* shareContext) { _context = new QOpenGLContext(); - if (PRIMARY) { - _context->setShareContext(PRIMARY->qglContext()); - } else { - PRIMARY = this; + _context->setFormat(_window->format()); + if (!shareContext) { + shareContext = qt_gl_global_share_context(); } - _context->setFormat(getDefaultOpenGLSurfaceFormat()); - _context->create(); + _context->setShareContext(shareContext); + _context->create(); _swapchainPixelSize = evalGLFormatSwapchainPixelSize(_context->format()); updateSwapchainMemoryCounter(); + + if (!makeCurrent()) { + throw std::runtime_error("Could not make context current"); + } + if (enableDebugLogger()) { + setupDebugLogging(_context); + } + doneCurrent(); + } #endif diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 7ebba4f8d8..c22bd0dde5 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -38,10 +38,15 @@ void gl::getTargetVersion(int& major, int& minor) { #if defined(USE_GLES) major = 3; minor = 2; +#else +#if defined(Q_OS_MAC) + major = 4; + minor = 1; #else major = 4; minor = disableGl45() ? 1 : 5; #endif +#endif } const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { @@ -57,6 +62,7 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { #else format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); #endif + format.setOption(QSurfaceFormat::DebugContext); // Qt Quick may need a depth and stencil buffer. Always make sure these are available. format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); @@ -64,7 +70,6 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { ::gl::getTargetVersion(major, minor); format.setMajorVersion(major); format.setMinorVersion(minor); - QSurfaceFormat::setDefaultFormat(format); }); return format; } diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index 1c0ad1a85e..94702a9906 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -63,10 +63,10 @@ int GLWidget::getDeviceHeight() const { return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f); } -void GLWidget::createContext() { +void GLWidget::createContext(QOpenGLContext* shareContext) { _context = new gl::Context(); _context->setWindow(windowHandle()); - _context->create(); + _context->create(shareContext); _context->makeCurrent(); _context->clear(); _context->doneCurrent(); diff --git a/libraries/gl/src/gl/GLWidget.h b/libraries/gl/src/gl/GLWidget.h index a0bf8ea0b0..777d43e8af 100644 --- a/libraries/gl/src/gl/GLWidget.h +++ b/libraries/gl/src/gl/GLWidget.h @@ -29,7 +29,7 @@ public: int getDeviceHeight() const; QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); } QPaintEngine* paintEngine() const override; - void createContext(); + void createContext(QOpenGLContext* shareContext = nullptr); bool makeCurrent(); void doneCurrent(); void swapBuffers(); diff --git a/libraries/gl/src/gl/GLWindow.cpp b/libraries/gl/src/gl/GLWindow.cpp index e1e6279b1c..7930e050de 100644 --- a/libraries/gl/src/gl/GLWindow.cpp +++ b/libraries/gl/src/gl/GLWindow.cpp @@ -22,7 +22,7 @@ void GLWindow::createContext(QOpenGLContext* shareContext) { void GLWindow::createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext) { _context = new gl::Context(); _context->setWindow(this); - _context->create(); + _context->create(shareContext); _context->makeCurrent(); _context->clear(); } diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 6598a26c99..1256e316ff 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -75,32 +75,9 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { } #endif - if (gl::Context::enableDebugLogger()) { - _context->makeCurrent(_offscreenSurface); - QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); - connect(logger, &QOpenGLDebugLogger::messageLogged, this, &OffscreenGLCanvas::onMessageLogged); - logger->initialize(); - logger->enableMessages(); - logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); - _context->doneCurrent(); - } - return true; } -void OffscreenGLCanvas::onMessageLogged(const QOpenGLDebugMessage& debugMessage) { - auto severity = debugMessage.severity(); - switch (severity) { - case QOpenGLDebugMessage::NotificationSeverity: - case QOpenGLDebugMessage::LowSeverity: - return; - default: - break; - } - qDebug(glLogging) << debugMessage; - return; -} - bool OffscreenGLCanvas::makeCurrent() { bool result = _context->makeCurrent(_offscreenSurface); if (glGetString) { diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h index a4960ae234..374720a910 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -35,9 +35,6 @@ public: void setThreadContext(); static bool restoreThreadContext(); -private slots: - void onMessageLogged(const QOpenGLDebugMessage &debugMessage); - protected: void clearThreadContext(); diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index 0b153a5ae8..fbebb1128d 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -13,6 +13,10 @@ #include +#ifdef Q_OS_WIN +#include +#endif + uint32_t QOpenGLContextWrapper::currentContextVersion() { QOpenGLContext* context = QOpenGLContext::currentContext(); if (!context) { @@ -45,6 +49,19 @@ void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) { _context->setFormat(format); } +#ifdef Q_OS_WIN +void* QOpenGLContextWrapper::nativeContext(QOpenGLContext* context) { + HGLRC result = 0; + if (context != nullptr) { + auto nativeHandle = context->nativeHandle(); + if (nativeHandle.canConvert()) { + result = nativeHandle.value().context(); + } + } + return result; +} +#endif + bool QOpenGLContextWrapper::create() { return _context->create(); } diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.h b/libraries/gl/src/gl/QOpenGLContextWrapper.h index b09ad1a4c3..32ba7f22e8 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.h +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.h @@ -13,6 +13,7 @@ #define hifi_QOpenGLContextWrapper_h #include +#include class QOpenGLContext; class QSurface; @@ -21,6 +22,10 @@ class QThread; class QOpenGLContextWrapper { public: +#ifdef Q_OS_WIN + static void* nativeContext(QOpenGLContext* context); +#endif + QOpenGLContextWrapper(); QOpenGLContextWrapper(QOpenGLContext* context); virtual ~QOpenGLContextWrapper(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 1203e65685..c1ce05c18b 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -708,37 +708,37 @@ void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) { void GLBackend::releaseBuffer(GLuint id, Size size) const { Lock lock(_trashMutex); - _buffersTrash.push_back({ id, size }); + _currentFrameTrash.buffersTrash.push_back({ id, size }); } void GLBackend::releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const { Lock lock(_trashMutex); - _externalTexturesTrash.push_back({ id, recycler }); + _currentFrameTrash.externalTexturesTrash.push_back({ id, recycler }); } void GLBackend::releaseTexture(GLuint id, Size size) const { Lock lock(_trashMutex); - _texturesTrash.push_back({ id, size }); + _currentFrameTrash.texturesTrash.push_back({ id, size }); } void GLBackend::releaseFramebuffer(GLuint id) const { Lock lock(_trashMutex); - _framebuffersTrash.push_back(id); + _currentFrameTrash.framebuffersTrash.push_back(id); } void GLBackend::releaseShader(GLuint id) const { Lock lock(_trashMutex); - _shadersTrash.push_back(id); + _currentFrameTrash.shadersTrash.push_back(id); } void GLBackend::releaseProgram(GLuint id) const { Lock lock(_trashMutex); - _programsTrash.push_back(id); + _currentFrameTrash.programsTrash.push_back(id); } void GLBackend::releaseQuery(GLuint id) const { Lock lock(_trashMutex); - _queriesTrash.push_back(id); + _currentFrameTrash.queriesTrash.push_back(id); } void GLBackend::queueLambda(const std::function lambda) const { @@ -746,6 +746,81 @@ void GLBackend::queueLambda(const std::function lambda) const { _lambdaQueue.push_back(lambda); } +void GLBackend::FrameTrash::cleanup() { + glWaitSync(fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(fence); + + { + std::vector ids; + ids.reserve(buffersTrash.size()); + for (auto pair : buffersTrash) { + ids.push_back(pair.first); + } + if (!ids.empty()) { + glDeleteBuffers((GLsizei)ids.size(), ids.data()); + } + } + + { + std::vector ids; + ids.reserve(framebuffersTrash.size()); + for (auto id : framebuffersTrash) { + ids.push_back(id); + } + if (!ids.empty()) { + glDeleteFramebuffers((GLsizei)ids.size(), ids.data()); + } + } + + { + std::vector ids; + ids.reserve(texturesTrash.size()); + for (auto pair : texturesTrash) { + ids.push_back(pair.first); + } + if (!ids.empty()) { + glDeleteTextures((GLsizei)ids.size(), ids.data()); + } + } + + { + if (!externalTexturesTrash.empty()) { + std::vector fences; + fences.resize(externalTexturesTrash.size()); + for (size_t i = 0; i < externalTexturesTrash.size(); ++i) { + fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + // External texture fences will be read in another thread/context, so we need a flush + glFlush(); + size_t index = 0; + for (auto pair : externalTexturesTrash) { + auto fence = fences[index++]; + pair.second(pair.first, fence); + } + } + } + + for (auto id : programsTrash) { + glDeleteProgram(id); + } + + for (auto id : shadersTrash) { + glDeleteShader(id); + } + + { + std::vector ids; + ids.reserve(queriesTrash.size()); + for (auto id : queriesTrash) { + ids.push_back(id); + } + if (!ids.empty()) { + glDeleteQueries((GLsizei)ids.size(), ids.data()); + } + } + +} + void GLBackend::recycle() const { PROFILE_RANGE(render_gpu_gl, __FUNCTION__) { @@ -759,112 +834,16 @@ void GLBackend::recycle() const { } } - { - std::vector ids; - std::list> buffersTrash; - { - Lock lock(_trashMutex); - std::swap(_buffersTrash, buffersTrash); - } - ids.reserve(buffersTrash.size()); - for (auto pair : buffersTrash) { - ids.push_back(pair.first); - } - if (!ids.empty()) { - glDeleteBuffers((GLsizei)ids.size(), ids.data()); - } + while (!_previousFrameTrashes.empty()) { + _previousFrameTrashes.front().cleanup(); + _previousFrameTrashes.pop_front(); } + _previousFrameTrashes.emplace_back(); { - std::vector ids; - std::list framebuffersTrash; - { - Lock lock(_trashMutex); - std::swap(_framebuffersTrash, framebuffersTrash); - } - ids.reserve(framebuffersTrash.size()); - for (auto id : framebuffersTrash) { - ids.push_back(id); - } - if (!ids.empty()) { - glDeleteFramebuffers((GLsizei)ids.size(), ids.data()); - } - } - - { - std::vector ids; - std::list> texturesTrash; - { - Lock lock(_trashMutex); - std::swap(_texturesTrash, texturesTrash); - } - ids.reserve(texturesTrash.size()); - for (auto pair : texturesTrash) { - ids.push_back(pair.first); - } - if (!ids.empty()) { - glDeleteTextures((GLsizei)ids.size(), ids.data()); - } - } - - { - std::list> externalTexturesTrash; - { - Lock lock(_trashMutex); - std::swap(_externalTexturesTrash, externalTexturesTrash); - } - if (!externalTexturesTrash.empty()) { - std::vector fences; - fences.resize(externalTexturesTrash.size()); - for (size_t i = 0; i < externalTexturesTrash.size(); ++i) { - fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - } - // External texture fences will be read in another thread/context, so we need a flush - glFlush(); - size_t index = 0; - for (auto pair : externalTexturesTrash) { - auto fence = fences[index++]; - pair.second(pair.first, fence); - } - } - } - - { - std::list programsTrash; - { - Lock lock(_trashMutex); - std::swap(_programsTrash, programsTrash); - } - for (auto id : programsTrash) { - glDeleteProgram(id); - } - } - - { - std::list shadersTrash; - { - Lock lock(_trashMutex); - std::swap(_shadersTrash, shadersTrash); - } - for (auto id : shadersTrash) { - glDeleteShader(id); - } - } - - { - std::vector ids; - std::list queriesTrash; - { - Lock lock(_trashMutex); - std::swap(_queriesTrash, queriesTrash); - } - ids.reserve(queriesTrash.size()); - for (auto id : queriesTrash) { - ids.push_back(id); - } - if (!ids.empty()) { - glDeleteQueries((GLsizei)ids.size(), ids.data()); - } + Lock lock(_trashMutex); + _previousFrameTrashes.back().swap(_currentFrameTrash); + _previousFrameTrashes.back().fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } _textureManagement._transferEngine->manageMemory(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index c309bcb864..37dde5b08e 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -419,16 +419,34 @@ protected: static const size_t INVALID_OFFSET = (size_t)-1; bool _inRenderTransferPass{ false }; int _currentDraw{ -1 }; - - std::list profileRanges; + + struct FrameTrash { + GLsync fence = nullptr; + std::list> buffersTrash; + std::list> texturesTrash; + std::list> externalTexturesTrash; + std::list framebuffersTrash; + std::list shadersTrash; + std::list programsTrash; + std::list queriesTrash; + + void swap(FrameTrash& other) { + buffersTrash.swap(other.buffersTrash); + texturesTrash.swap(other.texturesTrash); + externalTexturesTrash.swap(other.externalTexturesTrash); + framebuffersTrash.swap(other.framebuffersTrash); + shadersTrash.swap(other.shadersTrash); + programsTrash.swap(other.programsTrash); + queriesTrash.swap(other.queriesTrash); + } + + void cleanup(); + }; + mutable Mutex _trashMutex; - mutable std::list> _buffersTrash; - mutable std::list> _texturesTrash; - mutable std::list> _externalTexturesTrash; - mutable std::list _framebuffersTrash; - mutable std::list _shadersTrash; - mutable std::list _programsTrash; - mutable std::list _queriesTrash; + mutable FrameTrash _currentFrameTrash; + mutable std::list _previousFrameTrashes; + std::list profileRanges; mutable std::list> _lambdaQueue; void renderPassTransfer(const Batch& batch); diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 39f3123d40..8a56929e6b 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -139,9 +139,9 @@ void RenderEventHandler::onRender() { glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + // Fence will be used in another thread / context, so a flush is required glFlush(); _shared->updateTextureAndFence({ texture, fence }); - // Fence will be used in another thread / context, so a flush is required _shared->_quickWindow->resetOpenGLState(); } } @@ -167,4 +167,5 @@ void RenderEventHandler::onQuit() { moveToThread(qApp->thread()); QThread::currentThread()->quit(); } -#endif \ No newline at end of file + +#endif diff --git a/libraries/qml/src/qml/impl/TextureCache.cpp b/libraries/qml/src/qml/impl/TextureCache.cpp index 7af8fa1ac9..7b4fb3adaf 100644 --- a/libraries/qml/src/qml/impl/TextureCache.cpp +++ b/libraries/qml/src/qml/impl/TextureCache.cpp @@ -51,8 +51,10 @@ uint32_t TextureCache::acquireTexture(const QSize& size) { if (!textureSet.returnedTextures.empty()) { auto textureAndFence = textureSet.returnedTextures.front(); textureSet.returnedTextures.pop_front(); - glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED); - glDeleteSync((GLsync)textureAndFence.second); + if (textureAndFence.second) { + glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED); + glDeleteSync((GLsync)textureAndFence.second); + } return textureAndFence.first; } return createTexture(size); @@ -101,9 +103,11 @@ void TextureCache::destroyTexture(uint32_t texture) { void TextureCache::destroy(const Value& textureAndFence) { const auto& fence = textureAndFence.second; - // FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context. - glWaitSync((GLsync)fence, 0, GL_TIMEOUT_IGNORED); - glDeleteSync((GLsync)fence); + if (fence) { + // FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context. + glWaitSync((GLsync)fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync((GLsync)fence); + } destroyTexture(textureAndFence.first); } diff --git a/tests-manual/qml/qml/MacQml.qml b/tests-manual/qml/qml/MacQml.qml new file mode 100644 index 0000000000..bb7e3a0dff --- /dev/null +++ b/tests-manual/qml/qml/MacQml.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls 1.2 +import QtWebEngine 1.5 + +Item { + width: 640 + height: 480 + + Rectangle { + width: 5 + height: 5 + color: "red" + ColorAnimation on color { loops: Animation.Infinite; from: "red"; to: "yellow"; duration: 1000 } + } + + + WebEngineView { + id: root + url: "https://google.com/" + x: 6; y: 6; + width: parent.width * 0.8 + height: parent.height * 0.8 + + } +} diff --git a/tests-manual/qml/src/MacQml.cpp b/tests-manual/qml/src/MacQml.cpp new file mode 100644 index 0000000000..7f7854ce87 --- /dev/null +++ b/tests-manual/qml/src/MacQml.cpp @@ -0,0 +1,78 @@ +#include "MacQml.h" + +#include + +#include + +#include + +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; +// +//void MacQml::destroySurface(QmlInfo& qmlInfo) { +// auto& surface = qmlInfo.surface; +// auto& currentTexture = qmlInfo.texture; +// if (currentTexture) { +// auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +// glFlush(); +// _discardLamdba(currentTexture, readFence); +// } +// auto webView = surface->getRootItem(); +// if (webView) { +// // stop loading +// QMetaObject::invokeMethod(webView, "stop"); +// webView->setProperty(URL_PROPERTY, "about:blank"); +// } +// surface->pause(); +// surface.reset(); +//} + +void MacQml::update() { + auto rootItem =_surface->getRootItem(); + float now = sinf(secTimestampNow()); + rootItem->setProperty("level", abs(now)); + rootItem->setProperty("muted", now > 0.0f); + rootItem->setProperty("statsValue", rand()); + + // Fetch any new textures + TextureAndFence newTextureAndFence; + if (_surface->fetchTexture(newTextureAndFence)) { + if (_texture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(_texture, readFence); + } + _texture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } +} + +void MacQml::init() { + Parent::init(); + _glf.glGenFramebuffers(1, &_fbo); + _surface.reset(new hifi::qml::OffscreenSurface()); + //QUrl url =getTestResource("qml/main.qml"); + QUrl url = getTestResource("qml/MacQml.qml"); + hifi::qml::QmlContextObjectCallback callback =[](QQmlContext* context, QQuickItem* item) { + }; + _surface->load(url, callback); + _surface->resize(_window->size()); + _surface->resume(); + +} + +void MacQml::draw() { + auto size = _window->geometry().size(); + if (_texture) { + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); + _glf.glBlitFramebuffer( + // src coordinates + 0, 0, size.width(), size.height(), + // dst coordinates + 0, 0, size.width(), size.height(), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + } +} diff --git a/tests-manual/qml/src/MacQml.h b/tests-manual/qml/src/MacQml.h new file mode 100644 index 0000000000..50f71cb72e --- /dev/null +++ b/tests-manual/qml/src/MacQml.h @@ -0,0 +1,16 @@ +#include "TestCase.h" + +#include + +class MacQml : public TestCase { + using Parent = TestCase; +public: + GLuint _texture{ 0 }; + QmlPtr _surface; + GLuint _fbo{ 0 }; + + MacQml(const QWindow* window) : Parent(window) {} + void update() override; + void init() override; + void draw() override; +}; diff --git a/tests-manual/qml/src/StressWeb.cpp b/tests-manual/qml/src/StressWeb.cpp new file mode 100644 index 0000000000..71293feb9a --- /dev/null +++ b/tests-manual/qml/src/StressWeb.cpp @@ -0,0 +1,131 @@ +#include "StressWeb.h" + +#include + +#include + +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; + +static const int DEFAULT_MAX_FPS = 10; +static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; +static const char* URL_PROPERTY{ "url" }; + +QString StressWeb::getSourceUrl(bool video) { + static const std::vector SOURCE_URLS{ + "https://www.reddit.com/wiki/random", + "https://en.wikipedia.org/wiki/Wikipedia:Random", + "https://slashdot.org/", + }; + + static const std::vector VIDEO_SOURCE_URLS{ + "https://www.youtube.com/watch?v=gDXwhHm4GhM", + "https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; + auto index = rand() % sourceUrls.size(); + return sourceUrls[index]; +} + + + +void StressWeb::buildSurface(QmlInfo& qmlInfo, bool video) { + ++_surfaceCount; + auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); + auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); + qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); + qmlInfo.texture = 0; + qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); + qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl(video)); + }); + qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); + qmlInfo.surface->resize(_qmlSize); + qmlInfo.surface->resume(); +} + +void StressWeb::destroySurface(QmlInfo& qmlInfo) { + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + if (currentTexture) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + auto webView = surface->getRootItem(); + if (webView) { + // stop loading + QMetaObject::invokeMethod(webView, "stop"); + webView->setProperty(URL_PROPERTY, "about:blank"); + } + surface->pause(); + surface.reset(); +} + +void StressWeb::update() { + auto now = usecTimestampNow(); + // Fetch any new textures + for (size_t x = 0; x < DIVISIONS_X; ++x) { + for (size_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface) { + if (now < _createStopTime && randFloat() > 0.99f) { + buildSurface(qmlInfo, x == 0 && y == 0); + } else { + continue; + } + } + + if (now > qmlInfo.lifetime) { + destroySurface(qmlInfo); + continue; + } + + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + + TextureAndFence newTextureAndFence; + if (surface->fetchTexture(newTextureAndFence)) { + if (currentTexture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + currentTexture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } + } + } +} + +void StressWeb::init() { + Parent::init(); + _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); + _glf.glGenFramebuffers(1, &_fbo); +} + +void StressWeb::draw() { + auto size = _window->geometry().size(); + auto incrementX = size.width() / DIVISIONS_X; + auto incrementY = size.height() / DIVISIONS_Y; + + for (uint32_t x = 0; x < DIVISIONS_X; ++x) { + for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface || !qmlInfo.texture) { + continue; + } + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0); + _glf.glBlitFramebuffer( + // src coordinates + 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, + // dst coordinates + incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } + } + _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); +} diff --git a/tests-manual/qml/src/StressWeb.h b/tests-manual/qml/src/StressWeb.h new file mode 100644 index 0000000000..a68e34d0c1 --- /dev/null +++ b/tests-manual/qml/src/StressWeb.h @@ -0,0 +1,34 @@ +#include "TestCase.h" + +#include + +#include + +#define DIVISIONS_X 5 +#define DIVISIONS_Y 5 + +class StressWeb : public TestCase { + using Parent = TestCase; +public: + using QmlPtr = QSharedPointer; + + struct QmlInfo { + QmlPtr surface; + GLuint texture{ 0 }; + uint64_t lifetime{ 0 }; + }; + + size_t _surfaceCount{ 0 }; + uint64_t _createStopTime{ 0 }; + const QSize _qmlSize{ 640, 480 }; + std::array, DIVISIONS_X> _surfaces; + GLuint _fbo{ 0 }; + + StressWeb(const QWindow* window) : Parent(window) {} + static QString getSourceUrl(bool video); + void buildSurface(QmlInfo& qmlInfo, bool video); + void destroySurface(QmlInfo& qmlInfo); + void update() override; + void init() override; + void draw() override; +}; diff --git a/tests-manual/qml/src/TestCase.cpp b/tests-manual/qml/src/TestCase.cpp new file mode 100644 index 0000000000..534de71e51 --- /dev/null +++ b/tests-manual/qml/src/TestCase.cpp @@ -0,0 +1,25 @@ +#include "TestCase.h" + +#include +#include + +void TestCase::destroy() { +} +void TestCase::update() { +} + +void TestCase::init() { + _glf.initializeOpenGLFunctions(); + _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); +} + +QUrl TestCase::getTestResource(const QString& relativePath) { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Resources Path: " << dir; + } + return QUrl::fromLocalFile(dir + relativePath); +} diff --git a/tests-manual/qml/src/TestCase.h b/tests-manual/qml/src/TestCase.h new file mode 100644 index 0000000000..191eecb408 --- /dev/null +++ b/tests-manual/qml/src/TestCase.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include + +class TestCase { +public: + using QmlPtr = QSharedPointer; + using Builder = std::function; + TestCase(const QWindow* window) : _window(window) {} + virtual void init(); + virtual void destroy(); + virtual void update(); + virtual void draw() = 0; + static QUrl getTestResource(const QString& relativePath); + +protected: + QOpenGLFunctions_4_1_Core _glf; + const QWindow* _window; + std::function _discardLamdba; +}; diff --git a/tests-manual/qml/src/main.cpp b/tests-manual/qml/src/main.cpp index d70bb52dde..1d98ebf8c8 100644 --- a/tests-manual/qml/src/main.cpp +++ b/tests-manual/qml/src/main.cpp @@ -43,6 +43,11 @@ #include #include #include +#include +#include + +#include "TestCase.h" +#include "MacQml.h" namespace gl { extern void initModuleGl(); @@ -67,53 +72,37 @@ QUrl getTestResource(const QString& relativePath) { return QUrl::fromLocalFile(dir + relativePath); } -#define DIVISIONS_X 5 -#define DIVISIONS_Y 5 - using QmlPtr = QSharedPointer; using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; -struct QmlInfo { - QmlPtr surface; - GLuint texture{ 0 }; - uint64_t lifetime{ 0 }; -}; - class TestWindow : public QWindow { public: - TestWindow(); + TestWindow(const TestCase::Builder& caseBuilder); private: QOpenGLContext _glContext; OffscreenGLCanvas _sharedContext; - std::array, DIVISIONS_X> _surfaces; + TestCase* _testCase{ nullptr }; QOpenGLFunctions_4_1_Core _glf; - std::function _discardLamdba; QSize _size; - size_t _surfaceCount{ 0 }; - GLuint _fbo{ 0 }; - const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; - uint64_t _createStopTime; void initGl(); - void updateSurfaces(); - void buildSurface(QmlInfo& qmlInfo, bool allowVideo); - void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); void resizeEvent(QResizeEvent* ev) override; }; -TestWindow::TestWindow() { +TestWindow::TestWindow(const TestCase::Builder& builder) { Setting::init(); + + _testCase = builder(this); setSurfaceType(QSurface::OpenGLSurface); qmlRegisterType("Hifi", 1, 0, "TestItem"); show(); - _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); resize(QSize(800, 600)); @@ -129,162 +118,84 @@ TestWindow::TestWindow() { }); } +Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); +Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); +OffscreenGLCanvas* _chromiumShareContext{ nullptr}; void TestWindow::initGl() { _glContext.setFormat(format()); + + auto globalShareContext = qt_gl_global_share_context(); + if (globalShareContext) { + _glContext.setShareContext(globalShareContext); + globalShareContext->makeCurrent(this); + gl::Context::setupDebugLogging(globalShareContext); + globalShareContext->doneCurrent(); + } + if (!_glContext.create() || !_glContext.makeCurrent(this)) { qFatal("Unable to intialize Window GL context"); } + gl::Context::setupDebugLogging(&_glContext); gl::initModuleGl(); _glf.initializeOpenGLFunctions(); - _glf.glGenFramebuffers(1, &_fbo); if (!_sharedContext.create(&_glContext) || !_sharedContext.makeCurrent()) { qFatal("Unable to intialize Shared GL context"); } hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext()); - _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); + + if (!globalShareContext) { + _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext->setObjectName("ChromiumShareContext"); + _chromiumShareContext->create(&_glContext); + if (!_chromiumShareContext->makeCurrent()) { + qFatal("Unable to make chromium shared context current"); + } + + qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + _chromiumShareContext->doneCurrent(); + } + + // Restore the GL widget context + if (!_glContext.makeCurrent(this)) { + qFatal("Unable to make window context current"); + } + + _testCase->init(); } void TestWindow::resizeWindow(const QSize& size) { _size = size; } -static const int DEFAULT_MAX_FPS = 10; -static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; -static const char* URL_PROPERTY{ "url" }; - -QString getSourceUrl(bool video) { - static const std::vector SOURCE_URLS{ - "https://www.reddit.com/wiki/random", - "https://en.wikipedia.org/wiki/Wikipedia:Random", - "https://slashdot.org/", - }; - - static const std::vector VIDEO_SOURCE_URLS{ - "https://www.youtube.com/watch?v=gDXwhHm4GhM", - "https://www.youtube.com/watch?v=Ch_hoYPPeGc", - }; - - const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; - auto index = rand() % sourceUrls.size(); - return sourceUrls[index]; -} - -void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) { - ++_surfaceCount; - auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); - auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); - qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); - qmlInfo.texture = 0; - qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); - qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { - item->setProperty(URL_PROPERTY, getSourceUrl(video)); - }); - qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); - qmlInfo.surface->resize(_qmlSize); - qmlInfo.surface->resume(); -} - -void TestWindow::destroySurface(QmlInfo& qmlInfo) { - auto& surface = qmlInfo.surface; - auto& currentTexture = qmlInfo.texture; - if (currentTexture) { - auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - _discardLamdba(currentTexture, readFence); - } - auto webView = surface->getRootItem(); - if (webView) { - // stop loading - QMetaObject::invokeMethod(webView, "stop"); - webView->setProperty(URL_PROPERTY, "about:blank"); - } - surface->pause(); - surface.reset(); -} - -void TestWindow::updateSurfaces() { - auto now = usecTimestampNow(); - // Fetch any new textures - for (size_t x = 0; x < DIVISIONS_X; ++x) { - for (size_t y = 0; y < DIVISIONS_Y; ++y) { - auto& qmlInfo = _surfaces[x][y]; - if (!qmlInfo.surface) { - if (now < _createStopTime && randFloat() > 0.99f) { - buildSurface(qmlInfo, x == 0 && y == 0); - } else { - continue; - } - } - - if (now > qmlInfo.lifetime) { - destroySurface(qmlInfo); - continue; - } - - auto& surface = qmlInfo.surface; - auto& currentTexture = qmlInfo.texture; - - TextureAndFence newTextureAndFence; - if (surface->fetchTexture(newTextureAndFence)) { - if (currentTexture != 0) { - auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - _discardLamdba(currentTexture, readFence); - } - currentTexture = newTextureAndFence.first; - _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); - } - } - } -} - void TestWindow::draw() { if (_aboutToQuit) { return; } - + // Attempting to draw before we're visible and have a valid size will // produce GL errors. if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) { return; } - + static std::once_flag once; std::call_once(once, [&] { initGl(); }); - + if (!_glContext.makeCurrent(this)) { return; } - - updateSurfaces(); - - auto size = this->geometry().size(); - auto incrementX = size.width() / DIVISIONS_X; - auto incrementY = size.height() / DIVISIONS_Y; + + _testCase->update(); + + auto size = geometry().size(); _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - for (uint32_t x = 0; x < DIVISIONS_X; ++x) { - for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { - auto& qmlInfo = _surfaces[x][y]; - if (!qmlInfo.surface || !qmlInfo.texture) { - continue; - } - _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo); - _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0); - _glf.glBlitFramebuffer( - // src coordinates - 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, - // dst coordinates - incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), - // blit mask and filter - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - } - _glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - _glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + _testCase->draw(); + _glContext.swapBuffers(this); } @@ -292,19 +203,15 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { resizeWindow(ev->size()); } -int main(int argc, char** argv) { - QSurfaceFormat format; - format.setDepthBufferSize(24); - format.setStencilBufferSize(8); +int main(int argc, char** argv) { + auto format = getDefaultOpenGLSurfaceFormat(); format.setVersion(4, 1); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); - format.setOption(QSurfaceFormat::DebugContext); QSurfaceFormat::setDefaultFormat(format); - // setFormat(format); QGuiApplication app(argc, argv); - TestWindow window; + TestCase::Builder builder = [](const QWindow* window)->TestCase*{ return new MacQml(window); }; + TestWindow window(builder); return app.exec(); } From 28b45ce1d9932fbb93041be2b7542ab4a6538564 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 1 Oct 2018 15:13:11 -0700 Subject: [PATCH 252/276] Resolve compositing glitches --- interface/src/Application.cpp | 52 +++++++++++-------- .../display-plugins/OpenGLDisplayPlugin.cpp | 2 + libraries/gl/src/gl/GLHelpers.cpp | 35 +++++++++++++ libraries/gl/src/gl/GLHelpers.h | 3 ++ libraries/gl/src/gl/OffscreenGLCanvas.cpp | 6 ++- libraries/gl/src/gl/OffscreenGLCanvas.h | 2 + .../qml/src/qml/impl/RenderEventHandler.cpp | 3 ++ 7 files changed, 80 insertions(+), 23 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 700561325f..6f65a124dd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2736,7 +2736,36 @@ void Application::initializeGL() { // We have to account for this possibility by checking here for an existing // global share context auto globalShareContext = qt_gl_global_share_context(); - _glWidget->createContext(globalShareContext); + +#if !defined(DISABLE_QML) + // Build a shared canvas / context for the Chromium processes + if (!globalShareContext) { + // Chromium rendering uses some GL functions that prevent nSight from capturing + // frames, so we only create the shared context if nsight is NOT active. + if (!nsightActive()) { + _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext->setObjectName("ChromiumShareContext"); + auto format =QSurfaceFormat::defaultFormat(); +#ifdef Q_OS_MAC + // On mac, the primary shared OpenGL context must be a 3.2 core context, + // or chromium flips out and spews error spam (but renders fine) + format.setMajorVersion(3); + format.setMinorVersion(2); +#endif + _chromiumShareContext->setFormat(format); + _chromiumShareContext->create(); + if (!_chromiumShareContext->makeCurrent()) { + qCWarning(interfaceapp, "Unable to make chromium shared context current"); + } + globalShareContext = _chromiumShareContext->getContext(); + qt_gl_set_global_share_context(globalShareContext); + _chromiumShareContext->doneCurrent(); + } + } +#endif + + + _glWidget->createContext(globalShareContext); if (!_glWidget->makeCurrent()) { qCWarning(interfaceapp, "Unable to make window context current"); @@ -2749,27 +2778,6 @@ void Application::initializeGL() { if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) { qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text")); } - - // Build a shared canvas / context for the Chromium processes - if (!globalShareContext) { - // Chromium rendering uses some GL functions that prevent nSight from capturing - // frames, so we only create the shared context if nsight is NOT active. - if (!nsightActive()) { - _chromiumShareContext = new OffscreenGLCanvas(); - _chromiumShareContext->setObjectName("ChromiumShareContext"); - _chromiumShareContext->create(_glWidget->qglContext()); - if (!_chromiumShareContext->makeCurrent()) { - qCWarning(interfaceapp, "Unable to make chromium shared context current"); - } - globalShareContext = _chromiumShareContext->getContext(); - qt_gl_set_global_share_context(globalShareContext); - _chromiumShareContext->doneCurrent(); - // Restore the GL widget context - if (!_glWidget->makeCurrent()) { - qCWarning(interfaceapp, "Unable to make window context current"); - } - } - } #endif if (!globalShareContext) { diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 6448c6d3a1..6525672aee 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -179,7 +179,9 @@ public: _context->makeCurrent(); { PROFILE_RANGE(render, "PluginPresent") + gl::globalLock(); currentPlugin->present(); + gl::globalRelease(false); CHECK_GL_ERROR(); } _context->doneCurrent(); diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index c22bd0dde5..ba27a13f58 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -34,6 +34,41 @@ bool gl::disableGl45() { #endif } +#ifdef Q_OS_MAC +#define SERIALIZE_GL_RENDERING +#endif + +#ifdef SERIALIZE_GL_RENDERING + +// This terrible terrible hack brought to you by the complete lack of reasonable +// OpenGL debugging tools on OSX. Without this serialization code, the UI textures +// frequently become 'glitchy' and get composited onto the main scene in what looks +// like a partially rendered state. +// This looks very much like either state bleeding across the contexts, or bad +// synchronization for the shared OpenGL textures. However, previous attempts to resolve +// it, even with gratuitous use of glFinish hasn't improved the situation + +static std::mutex _globalOpenGLLock; + +void gl::globalLock() { + _globalOpenGLLock.lock(); +} + +void gl::globalRelease(bool finish) { + if (finish) { + glFinish(); + } + _globalOpenGLLock.unlock(); +} + +#else + +void gl::globalLock() {} +void gl::globalRelease(bool finish) {} + +#endif + + void gl::getTargetVersion(int& major, int& minor) { #if defined(USE_GLES) major = 3; diff --git a/libraries/gl/src/gl/GLHelpers.h b/libraries/gl/src/gl/GLHelpers.h index 6252eba2f0..1865442ef6 100644 --- a/libraries/gl/src/gl/GLHelpers.h +++ b/libraries/gl/src/gl/GLHelpers.h @@ -35,6 +35,9 @@ int glVersionToInteger(QString glVersion); bool isRenderThread(); namespace gl { + void globalLock(); + void globalRelease(bool finish = true); + void withSavedContext(const std::function& f); bool checkGLError(const char* name); diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 1256e316ff..37289745a4 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -33,6 +33,7 @@ OffscreenGLCanvas::OffscreenGLCanvas() : _context(new QOpenGLContext), _offscreenSurface(new QOffscreenSurface) { + setFormat(getDefaultOpenGLSurfaceFormat()); } OffscreenGLCanvas::~OffscreenGLCanvas() { @@ -49,12 +50,15 @@ OffscreenGLCanvas::~OffscreenGLCanvas() { } +void OffscreenGLCanvas::setFormat(const QSurfaceFormat& format) { + _context->setFormat(format); +} + bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { if (nullptr != sharedContext) { sharedContext->doneCurrent(); _context->setShareContext(sharedContext); } - _context->setFormat(getDefaultOpenGLSurfaceFormat()); if (!_context->create()) { qFatal("Failed to create OffscreenGLCanvas context"); } diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h index 374720a910..3946f760cf 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -18,11 +18,13 @@ class QOpenGLContext; class QOffscreenSurface; class QOpenGLDebugMessage; +class QSurfaceFormat; class OffscreenGLCanvas : public QObject { public: OffscreenGLCanvas(); ~OffscreenGLCanvas(); + void setFormat(const QSurfaceFormat& format); bool create(QOpenGLContext* sharedContext = nullptr); bool makeCurrent(); void doneCurrent(); diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 8a56929e6b..46fc3490a7 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -12,6 +12,7 @@ #include #include +#include #include @@ -114,6 +115,7 @@ void RenderEventHandler::onRender() { PROFILE_RANGE(render_qml_gl, __FUNCTION__); + gl::globalLock(); if (!_shared->preRender()) { return; } @@ -144,6 +146,7 @@ void RenderEventHandler::onRender() { _shared->updateTextureAndFence({ texture, fence }); _shared->_quickWindow->resetOpenGLState(); } + gl::globalRelease(); } void RenderEventHandler::onQuit() { From 405ec228b8cae4aac0d340f6f132ddf825c6fb23 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 1 Oct 2018 16:07:02 -0700 Subject: [PATCH 253/276] Fix mac warning --- tests-manual/qml/src/MacQml.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests-manual/qml/src/MacQml.cpp b/tests-manual/qml/src/MacQml.cpp index 7f7854ce87..13d8642822 100644 --- a/tests-manual/qml/src/MacQml.cpp +++ b/tests-manual/qml/src/MacQml.cpp @@ -29,7 +29,7 @@ using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; void MacQml::update() { auto rootItem =_surface->getRootItem(); float now = sinf(secTimestampNow()); - rootItem->setProperty("level", abs(now)); + rootItem->setProperty("level", fabs(now)); rootItem->setProperty("muted", now > 0.0f); rootItem->setProperty("statsValue", rand()); From 9a30073f27b0786186a8214e94cd20c27db8ea2b Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 2 Oct 2018 09:20:41 -0700 Subject: [PATCH 254/276] Only enable debug GL context on demand --- libraries/gl/src/gl/Context.cpp | 7 +++++-- libraries/gl/src/gl/GLHelpers.cpp | 6 +++++- tests-manual/qml/src/MacQml.cpp | 18 ------------------ 3 files changed, 10 insertions(+), 21 deletions(-) diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index cd2b19beec..a3e01018b5 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -31,6 +31,10 @@ using namespace gl; bool Context::enableDebugLogger() { +#if defined(Q_OS_MAC) + // OSX does not support GL_KHR_debug or GL_ARB_debug_output + return false; +#else #if defined(DEBUG) || defined(USE_GLES) static bool enableDebugLogger = true; #else @@ -45,6 +49,7 @@ bool Context::enableDebugLogger() { } }); return enableDebugLogger; +#endif } @@ -233,7 +238,6 @@ GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context(); - void Context::create(QOpenGLContext* shareContext) { assert(0 != _hwnd); assert(0 == _hdc); @@ -351,7 +355,6 @@ void Context::create(QOpenGLContext* shareContext) { #endif - OffscreenContext::~OffscreenContext() { _window->deleteLater(); } diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index ba27a13f58..15a41c3dc1 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -13,6 +13,8 @@ #include #include +#include "Context.h" + size_t evalGLFormatSwapchainPixelSize(const QSurfaceFormat& format) { size_t pixelSize = format.redBufferSize() + format.greenBufferSize() + format.blueBufferSize() + format.alphaBufferSize(); // We don't apply the length of the swap chain into this pixelSize since it is not vsible for the Process (on windows). @@ -97,7 +99,9 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() { #else format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); #endif - format.setOption(QSurfaceFormat::DebugContext); + if (gl::Context::enableDebugLogger()) { + format.setOption(QSurfaceFormat::DebugContext); + } // Qt Quick may need a depth and stencil buffer. Always make sure these are available. format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS); format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS); diff --git a/tests-manual/qml/src/MacQml.cpp b/tests-manual/qml/src/MacQml.cpp index 13d8642822..9c5f91041e 100644 --- a/tests-manual/qml/src/MacQml.cpp +++ b/tests-manual/qml/src/MacQml.cpp @@ -7,24 +7,6 @@ #include using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; -// -//void MacQml::destroySurface(QmlInfo& qmlInfo) { -// auto& surface = qmlInfo.surface; -// auto& currentTexture = qmlInfo.texture; -// if (currentTexture) { -// auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); -// glFlush(); -// _discardLamdba(currentTexture, readFence); -// } -// auto webView = surface->getRootItem(); -// if (webView) { -// // stop loading -// QMetaObject::invokeMethod(webView, "stop"); -// webView->setProperty(URL_PROPERTY, "about:blank"); -// } -// surface->pause(); -// surface.reset(); -//} void MacQml::update() { auto rootItem =_surface->getRootItem(); From 7ca8b540399586683b075b4ab787bbf7a046e16f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Oct 2018 14:34:23 -0700 Subject: [PATCH 255/276] Fix crash on startup on windows / android --- libraries/gl/src/gl/Context.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index a3e01018b5..d4ecbaa5ba 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -41,13 +41,6 @@ bool Context::enableDebugLogger() { static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL"); static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); #endif - static std::once_flag once; - std::call_once(once, [&] { - // If the previous run crashed, force GL debug logging on - if (qApp->property(hifi::properties::CRASHED).toBool()) { - enableDebugLogger = true; - } - }); return enableDebugLogger; #endif } From 36edb939c3dbe7f72563fe590f41046d7863e345 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 25 Oct 2018 16:28:30 -0700 Subject: [PATCH 256/276] Try to fix android crash --- interface/src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 12a02ffd32..5af0a9371d 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -41,8 +41,8 @@ extern "C" { #endif int main(int argc, const char* argv[]) { - auto format = getDefaultOpenGLSurfaceFormat(); #ifdef Q_OS_MAC + auto format = getDefaultOpenGLSurfaceFormat(); // Deal with some weirdness in the chromium context sharing on Mac. // The primary share context needs to be 3.2, so that the Chromium will // succeed in it's creation of it's command stub contexts. @@ -51,8 +51,8 @@ int main(int argc, const char* argv[]) { // idea why. qputenv("QT_ENABLE_GLYPH_CACHE_WORKAROUND", "true"); // https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg -#endif QSurfaceFormat::setDefaultFormat(format); +#endif setupHifiApplication(BuildInfo::INTERFACE_NAME); QStringList arguments; From a3a952265a7e8d6cf42c43676a7be33717162740 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 26 Oct 2018 11:23:04 -0700 Subject: [PATCH 257/276] Fix android crash --- .../display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp | 1 + libraries/gl/src/gl/OffscreenGLCanvas.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 6525672aee..190d4d4104 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -88,6 +88,7 @@ public: // Move the OpenGL context to the present thread // Extra code because of the widget 'wrapper' context _context = context; + _context->doneCurrent(); _context->moveToThread(this); } diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 37289745a4..f05acb50e9 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -73,6 +73,7 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) { if (!_context->makeCurrent(_offscreenSurface)) { qFatal("Unable to make offscreen surface current"); } + _context->doneCurrent(); #else if (!_offscreenSurface->isValid()) { qFatal("Offscreen surface is invalid"); From 1cca975b3713d8a1bca814f36b8600b2ccfa9064 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 27 Oct 2018 11:40:02 +1300 Subject: [PATCH 258/276] Fix Interface eating memory when minimized --- interface/src/Application.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 58aad654db..e54d2e0961 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6017,7 +6017,9 @@ void Application::update(float deltaTime) { { PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); PerformanceTimer perfTimer("overlays"); - _overlays.update(deltaTime); + if (qApp->shouldPaint()) { + _overlays.update(deltaTime); + } } // Update _viewFrustum with latest camera and view frustum data... @@ -6102,8 +6104,10 @@ void Application::update(float deltaTime) { PROFILE_RANGE_EX(app, "PostUpdateLambdas", 0xffff0000, (uint64_t)0); PerformanceTimer perfTimer("postUpdateLambdas"); std::unique_lock guard(_postUpdateLambdasLock); - for (auto& iter : _postUpdateLambdas) { - iter.second(); + if (qApp->shouldPaint()) { + for (auto& iter : _postUpdateLambdas) { + iter.second(); + } } _postUpdateLambdas.clear(); } From 62e4b3feadb32c1b98e77429d20dad7d502c5679 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 26 Oct 2018 16:02:17 -0700 Subject: [PATCH 259/276] Avatar mixer gets the updated position --- libraries/avatars/src/AvatarData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index fcbe05ca52..aa3f83a33f 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1110,7 +1110,7 @@ public: QJsonObject toJson() const; void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); - glm::vec3 getClientGlobalPosition() const { return _globalPosition; } + glm::vec3 getClientGlobalPosition() const { return _serverPosition; } AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } /**jsdoc From 767f4e9d6dfb2537362a1cbb9f4da627de0a15d0 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 26 Oct 2018 17:16:43 -0700 Subject: [PATCH 260/276] Return server position --- libraries/avatars/src/AvatarData.cpp | 6 +++--- libraries/avatars/src/AvatarData.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f3b8d29658..42c7afc43b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -883,6 +883,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } _serverPosition = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; + if (_globalPosition != _serverPosition) { + _globalPositionChanged = now; + } auto oneStepDistance = glm::length(_globalPosition - _serverPosition); if (oneStepDistance <= AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE || oneStepDistance >= AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE) { _globalPosition = _serverPosition; @@ -891,9 +894,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { setLocalPosition(_serverPosition); } } - if (_globalPosition != _serverPosition) { - _globalPositionChanged = now; - } sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); int numBytesRead = sourceBuffer - startSection; _globalPositionRate.increment(numBytesRead); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index aa3f83a33f..32e75a64a9 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1111,7 +1111,7 @@ public: void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); glm::vec3 getClientGlobalPosition() const { return _serverPosition; } - AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } + AABox getGlobalBoundingBox() const { return AABox(_serverPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } /**jsdoc * @function MyAvatar.getAvatarEntityData From 056fe338e17262708f043e981184eb2f336eb720 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sat, 27 Oct 2018 07:15:31 -0700 Subject: [PATCH 261/276] Apply transit logic if avatar instance is client side --- interface/src/avatar/AvatarManager.cpp | 1 + libraries/avatars/src/AvatarData.cpp | 24 +++++++++++++++++------- libraries/avatars/src/AvatarData.h | 6 ++++-- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index dad4c44f4b..abc5185377 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -266,6 +266,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); + avatar->setIsClientAvatar(true); if (avatar->needsPhysicsUpdate()) { _avatarsToChangeInPhysics.insert(avatar); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 42c7afc43b..4f936900b0 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -883,13 +883,23 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } _serverPosition = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset; - if (_globalPosition != _serverPosition) { - _globalPositionChanged = now; - } - auto oneStepDistance = glm::length(_globalPosition - _serverPosition); - if (oneStepDistance <= AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE || oneStepDistance >= AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE) { - _globalPosition = _serverPosition; - // if we don't have a parent, make sure to also set our local position + if (_isClientAvatar) { + auto oneStepDistance = glm::length(_globalPosition - _serverPosition); + if (oneStepDistance <= AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE || oneStepDistance >= AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE) { + _globalPosition = _serverPosition; + // if we don't have a parent, make sure to also set our local position + if (!hasParent()) { + setLocalPosition(_serverPosition); + } + } + if (_globalPosition != _serverPosition) { + _globalPositionChanged = now; + } + } else { + if (_globalPosition != _serverPosition) { + _globalPosition = _serverPosition; + _globalPositionChanged = now; + } if (!hasParent()) { setLocalPosition(_serverPosition); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 32e75a64a9..e8858825d8 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1110,8 +1110,8 @@ public: QJsonObject toJson() const; void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); - glm::vec3 getClientGlobalPosition() const { return _serverPosition; } - AABox getGlobalBoundingBox() const { return AABox(_serverPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } + glm::vec3 getClientGlobalPosition() const { return _globalPosition; } + AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); } /**jsdoc * @function MyAvatar.getAvatarEntityData @@ -1206,6 +1206,7 @@ public: void setIsNewAvatar(bool isNewAvatar) { _isNewAvatar = isNewAvatar; } bool getIsNewAvatar() { return _isNewAvatar; } + void setIsClientAvatar(bool isClientAvatar) { _isClientAvatar = isClientAvatar; } signals: @@ -1469,6 +1470,7 @@ protected: float _density; int _replicaIndex { 0 }; bool _isNewAvatar { true }; + bool _isClientAvatar { false }; // null unless MyAvatar or ScriptableAvatar sending traits data to mixer std::unique_ptr _clientTraitsHandler; From e49e9cdc5608568b7ffd1816cc9127a1fb8aaddc Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 29 Oct 2018 07:25:22 -0700 Subject: [PATCH 262/276] Set avatar client sooner --- interface/src/avatar/AvatarManager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index abc5185377..aa57336a3c 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -261,12 +261,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) { const SortableAvatar& sortData = *it; const auto avatar = std::static_pointer_cast(sortData.getAvatar()); - + if (!avatar->_isClientAvatar) { + avatar->setIsClientAvatar(true); + } // TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); - avatar->setIsClientAvatar(true); if (avatar->needsPhysicsUpdate()) { _avatarsToChangeInPhysics.insert(avatar); } From df005eedbf7867fa886bdb8fc13c0e0fc09c049d Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 29 Oct 2018 17:43:16 -0700 Subject: [PATCH 263/276] fix shape not getting marked changed --- libraries/entities/src/EntityItemProperties.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index d901592759..c243f772e2 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -3207,6 +3207,8 @@ void EntityItemProperties::markAllChanged() { _queryAACubeChanged = true; + _shapeChanged = true; + _flyingAllowedChanged = true; _ghostingAllowedChanged = true; _filterURLChanged = true; From e64a4d0536be1a0f59b8b9c2d9739b6e4b7ec5b8 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 30 Oct 2018 12:47:44 -0700 Subject: [PATCH 264/276] try to fix unnecessary queryAACube updates --- libraries/shared/src/SpatiallyNestable.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index fd2ff6e790..97e20f5627 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -74,10 +74,12 @@ void SpatiallyNestable::setParentID(const QUuid& parentID) { } }); - bool success = false; - auto parent = getParentPointer(success); - if (success && parent) { - parent->updateQueryAACube(); + if (!_parentKnowsMe) { + bool success = false; + auto parent = getParentPointer(success); + if (success && parent) { + parent->updateQueryAACube(); + } } } From becee7f010571d3ffd61deeb062d16a73a06e2a1 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 30 Oct 2018 17:28:42 -0700 Subject: [PATCH 265/276] Re-name FBXGeometry to HFMGeometry and do the same for related classes --- .../src/avatars/ScriptableAvatar.cpp | 12 +- interface/src/ModelPackager.cpp | 4 +- interface/src/ModelPackager.h | 6 +- interface/src/ModelPropertiesDialog.cpp | 4 +- interface/src/ModelPropertiesDialog.h | 4 +- interface/src/avatar/MyAvatar.cpp | 14 +- interface/src/avatar/MySkeletonModel.cpp | 2 +- interface/src/raypick/CollisionPick.cpp | 30 ++-- interface/src/ui/overlays/ModelOverlay.cpp | 22 +-- libraries/animation/src/AnimClip.cpp | 18 +-- libraries/animation/src/AnimSkeleton.cpp | 16 +- libraries/animation/src/AnimSkeleton.h | 8 +- libraries/animation/src/AnimationCache.cpp | 18 +-- libraries/animation/src/AnimationCache.h | 12 +- libraries/animation/src/AnimationObject.cpp | 8 +- libraries/animation/src/AnimationObject.h | 4 +- libraries/animation/src/Rig.cpp | 18 +-- libraries/animation/src/Rig.h | 22 +-- .../src/avatars-renderer/Avatar.cpp | 6 +- .../src/avatars-renderer/SkeletonModel.cpp | 16 +- .../src/avatars-renderer/SkeletonModel.h | 4 +- libraries/baking/src/FBXBaker.cpp | 6 +- libraries/baking/src/FBXBaker.h | 2 +- libraries/baking/src/ModelBaker.cpp | 2 +- libraries/baking/src/ModelBaker.h | 2 +- libraries/baking/src/OBJBaker.cpp | 8 +- libraries/baking/src/OBJBaker.h | 4 +- .../src/RenderableModelEntityItem.cpp | 66 ++++---- libraries/fbx/src/FBX.h | 90 +++++------ libraries/fbx/src/FBXReader.cpp | 150 +++++++++--------- libraries/fbx/src/FBXReader.h | 16 +- libraries/fbx/src/FBXReader_Material.cpp | 40 ++--- libraries/fbx/src/FBXReader_Mesh.cpp | 76 ++++----- libraries/fbx/src/GLTFReader.cpp | 62 ++++---- libraries/fbx/src/GLTFReader.h | 10 +- libraries/fbx/src/OBJReader.cpp | 84 +++++----- libraries/fbx/src/OBJReader.h | 12 +- .../src/model-networking/ModelCache.cpp | 54 +++---- .../src/model-networking/ModelCache.h | 14 +- .../render-utils/src/CauterizedModel.cpp | 18 +-- .../render-utils/src/MeshPartPayload.cpp | 4 +- libraries/render-utils/src/Model.cpp | 76 ++++----- libraries/render-utils/src/Model.h | 6 +- .../render-utils/src/SoftAttachmentModel.cpp | 6 +- tests-manual/gpu/src/TestFbx.cpp | 2 +- tests-manual/gpu/src/TestFbx.h | 2 +- .../src/AnimInverseKinematicsTests.cpp | 8 +- tools/skeleton-dump/src/SkeletonDumpApp.cpp | 4 +- tools/vhacd-util/src/VHACDUtil.cpp | 44 ++--- tools/vhacd-util/src/VHACDUtil.h | 12 +- tools/vhacd-util/src/VHACDUtilApp.cpp | 26 +-- tools/vhacd-util/src/VHACDUtilApp.h | 2 +- 52 files changed, 578 insertions(+), 578 deletions(-) diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 7d2b267a05..385f94d9f3 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -69,10 +69,10 @@ void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); } -static AnimPose composeAnimPose(const FBXJoint& fbxJoint, const glm::quat rotation, const glm::vec3 translation) { +static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, const glm::vec3 translation) { glm::mat4 translationMat = glm::translate(translation); - glm::mat4 rotationMat = glm::mat4_cast(fbxJoint.preRotation * rotation * fbxJoint.postRotation); - glm::mat4 finalMat = translationMat * fbxJoint.preTransform * rotationMat * fbxJoint.postTransform; + glm::mat4 rotationMat = glm::mat4_cast(joint.preRotation * rotation * joint.postRotation); + glm::mat4 finalMat = translationMat * joint.preTransform * rotationMat * joint.postTransform; return AnimPose(finalMat); } @@ -93,7 +93,7 @@ void ScriptableAvatar::update(float deltatime) { } _animationDetails.currentFrame = currentFrame; - const QVector& modelJoints = _bind->getGeometry().joints; + const QVector& modelJoints = _bind->getGeometry().joints; QStringList animationJointNames = _animation->getJointNames(); const int nJoints = modelJoints.size(); @@ -102,8 +102,8 @@ void ScriptableAvatar::update(float deltatime) { } const int frameCount = _animation->getFrames().size(); - const FBXAnimationFrame& floorFrame = _animation->getFrames().at((int)glm::floor(currentFrame) % frameCount); - const FBXAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(currentFrame) % frameCount); + const HFMAnimationFrame& floorFrame = _animation->getFrames().at((int)glm::floor(currentFrame) % frameCount); + const HFMAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(currentFrame) % frameCount); const float frameFraction = glm::fract(currentFrame); std::vector poses = _animSkeleton->getRelativeDefaultPoses(); diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 3a5d92eb8c..0b2846006b 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -235,7 +235,7 @@ bool ModelPackager::zipModel() { return true; } -void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) { +void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const HFMGeometry& geometry) { bool isBodyType = _modelType == FSTReader::BODY_ONLY_MODEL || _modelType == FSTReader::HEAD_AND_BODY_MODEL; @@ -370,7 +370,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename void ModelPackager::listTextures() { _textures.clear(); - foreach (const FBXMaterial mat, _geometry->materials) { + foreach (const HFMMaterial mat, _geometry->materials) { if (!mat.albedoTexture.filename.isEmpty() && mat.albedoTexture.content.isEmpty() && !_textures.contains(mat.albedoTexture.filename)) { _textures << mat.albedoTexture.filename; diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h index 76295e5a85..b68d9e746d 100644 --- a/interface/src/ModelPackager.h +++ b/interface/src/ModelPackager.h @@ -19,7 +19,7 @@ #include "ui/ModelsBrowser.h" -class FBXGeometry; +class HFMGeometry; class ModelPackager : public QObject { public: @@ -32,7 +32,7 @@ private: bool editProperties(); bool zipModel(); - void populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry); + void populateBasicMapping(QVariantHash& mapping, QString filename, const HFMGeometry& geometry); void listTextures(); bool copyTextures(const QString& oldDir, const QDir& newDir); @@ -44,7 +44,7 @@ private: QString _scriptDir; QVariantHash _mapping; - std::unique_ptr _geometry; + std::unique_ptr _geometry; QStringList _textures; QStringList _scripts; }; diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index 8984f89d07..dcda85d117 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -27,7 +27,7 @@ ModelPropertiesDialog::ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping, - const QString& basePath, const FBXGeometry& geometry) : + const QString& basePath, const HFMGeometry& geometry) : _modelType(modelType), _originalMapping(originalMapping), _basePath(basePath), @@ -249,7 +249,7 @@ QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const { if (withNone) { box->addItem("(none)"); } - foreach (const FBXJoint& joint, _geometry.joints) { + foreach (const HFMJoint& joint, _geometry.joints) { if (joint.isSkeletonJoint || !_geometry.hasSkeletonJoints) { box->addItem(joint.name); } diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h index e3c2d8ed6a..d1a020bec3 100644 --- a/interface/src/ModelPropertiesDialog.h +++ b/interface/src/ModelPropertiesDialog.h @@ -30,7 +30,7 @@ class ModelPropertiesDialog : public QDialog { public: ModelPropertiesDialog(FSTReader::ModelType modelType, const QVariantHash& originalMapping, - const QString& basePath, const FBXGeometry& geometry); + const QString& basePath, const HFMGeometry& geometry); QVariantHash getMapping() const; @@ -50,7 +50,7 @@ private: FSTReader::ModelType _modelType; QVariantHash _originalMapping; QString _basePath; - FBXGeometry _geometry; + HFMGeometry _geometry; QLineEdit* _name = nullptr; QPushButton* _textureDirectory = nullptr; QPushButton* _scriptDirectory = nullptr; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3299bd10e7..b3a66f70b8 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -155,7 +155,7 @@ MyAvatar::MyAvatar(QThread* thread) : }); connect(_skeletonModel.get(), &Model::rigReady, this, [this]() { if (_shouldLoadScripts) { - auto geometry = getSkeletonModel()->getFBXGeometry(); + auto geometry = getSkeletonModel()->getHFMGeometry(); qApp->loadAvatarScripts(geometry.scripts); _shouldLoadScripts = false; } @@ -2429,10 +2429,10 @@ void MyAvatar::attachmentDataToEntityProperties(const AttachmentData& data, Enti void MyAvatar::initHeadBones() { int neckJointIndex = -1; if (_skeletonModel->isLoaded()) { - neckJointIndex = _skeletonModel->getFBXGeometry().neckJointIndex; + neckJointIndex = _skeletonModel->getHFMGeometry().neckJointIndex; } if (neckJointIndex == -1) { - neckJointIndex = (_skeletonModel->getFBXGeometry().headJointIndex - 1); + neckJointIndex = (_skeletonModel->getHFMGeometry().headJointIndex - 1); if (neckJointIndex < 0) { // return if the head is not even there. can't cauterize!! return; @@ -2443,7 +2443,7 @@ void MyAvatar::initHeadBones() { q.push(neckJointIndex); _headBoneSet.insert(neckJointIndex); - // fbxJoints only hold links to parents not children, so we have to do a bit of extra work here. + // hfmJoints only hold links to parents not children, so we have to do a bit of extra work here. while (q.size() > 0) { int jointIndex = q.front(); for (int i = 0; i < _skeletonModel->getJointStateCount(); i++) { @@ -2592,11 +2592,11 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { if (_skeletonModel && _skeletonModel->isLoaded()) { const Rig& rig = _skeletonModel->getRig(); - const FBXGeometry& geometry = _skeletonModel->getFBXGeometry(); + const HFMGeometry& geometry = _skeletonModel->getHFMGeometry(); for (int i = 0; i < rig.getJointStateCount(); i++) { AnimPose jointPose; rig.getAbsoluteJointPoseInRigFrame(i, jointPose); - const FBXJointShapeInfo& shapeInfo = geometry.joints[i].shapeInfo; + const HFMJointShapeInfo& shapeInfo = geometry.joints[i].shapeInfo; const AnimPose pose = rigToWorldPose * jointPose; for (size_t j = 0; j < shapeInfo.debugLines.size() / 2; j++) { glm::vec3 pointA = pose.xformPoint(shapeInfo.debugLines[2 * j]); @@ -4012,7 +4012,7 @@ float MyAvatar::getSitStandStateChange() const { } QVector MyAvatar::getScriptUrls() { - QVector scripts = _skeletonModel->isLoaded() ? _skeletonModel->getFBXGeometry().scripts : QVector(); + QVector scripts = _skeletonModel->isLoaded() ? _skeletonModel->getHFMGeometry().scripts : QVector(); return scripts; } diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index c6aae6124a..3ec40d372b 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -90,7 +90,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { // Called within Model::simulate call, below. void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); Head* head = _owningAvatar->getHead(); diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 25927c5b68..e8a53aa9b6 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -131,7 +131,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha // should never fall in here when collision model not fully loaded // TODO: assert that all geometries exist and are loaded //assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); - const FBXGeometry& collisionGeometry = resource->getFBXGeometry(); + const HFMGeometry& collisionGeometry = resource->getHFMGeometry(); ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); pointCollection.clear(); @@ -139,15 +139,15 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. - foreach (const FBXMesh& mesh, collisionGeometry.meshes) { + foreach (const HFMMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { pointCollection.push_back(QVector()); ShapeInfo::PointList& pointsInPart = pointCollection[i]; // run through all the triangles and (uniquely) add each point to the hull uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -168,7 +168,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha // run through all the quads and (uniquely) add each point to the hull numIndices = (uint32_t)meshPart.quadIndices.size(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % QUAD_STRIDE == 0); numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -206,7 +206,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha // to the visual model and apply them to the collision model (without regard for the // collision model's extents). - glm::vec3 scaleToFit = dimensions / resource->getFBXGeometry().getUnscaledMeshExtents().size(); + glm::vec3 scaleToFit = dimensions / resource->getHFMGeometry().getUnscaledMeshExtents().size(); // multiply each point by scale for (int32_t i = 0; i < pointCollection.size(); i++) { for (int32_t j = 0; j < pointCollection[i].size(); j++) { @@ -216,11 +216,11 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha } shapeInfo.setParams(type, dimensions, resource->getURL().toString()); } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { - const FBXGeometry& fbxGeometry = resource->getFBXGeometry(); - int numFbxMeshes = fbxGeometry.meshes.size(); + const HFMGeometry& hfmGeometry = resource->getHFMGeometry(); + int numHFMMeshes = hfmGeometry.meshes.size(); int totalNumVertices = 0; - for (int i = 0; i < numFbxMeshes; i++) { - const FBXMesh& mesh = fbxGeometry.meshes.at(i); + for (int i = 0; i < numHFMMeshes; i++) { + const HFMMesh& mesh = hfmGeometry.meshes.at(i); totalNumVertices += mesh.vertices.size(); } const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; @@ -230,7 +230,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha return; } - auto& meshes = resource->getFBXGeometry().meshes; + auto& meshes = resource->getHFMGeometry().meshes; int32_t numMeshes = (int32_t)(meshes.size()); const int MAX_ALLOWED_MESH_COUNT = 1000; @@ -285,12 +285,12 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha if (type == SHAPE_TYPE_STATIC_MESH) { // copy into triangleIndices size_t triangleIndicesCount = 0; - for (const FBXMeshPart& meshPart : mesh.parts) { + for (const HFMMeshPart& meshPart : mesh.parts) { triangleIndicesCount += meshPart.triangleIndices.count(); } triangleIndices.reserve((int)triangleIndicesCount); - for (const FBXMeshPart& meshPart : mesh.parts) { + for (const HFMMeshPart& meshPart : mesh.parts) { const int* indexItr = meshPart.triangleIndices.cbegin(); while (indexItr != meshPart.triangleIndices.cend()) { triangleIndices.push_back(*indexItr); @@ -299,11 +299,11 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha } } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { // for each mesh copy unique part indices, separated by special bogus (flag) index values - for (const FBXMeshPart& meshPart : mesh.parts) { + for (const HFMMeshPart& meshPart : mesh.parts) { // collect unique list of indices for this part std::set uniqueIndices; auto numIndices = meshPart.triangleIndices.count(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices% TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index eee8222051..1b66ff08ad 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -446,7 +446,7 @@ QVariant ModelOverlay::getProperty(const QString& property) { if (property == "jointNames") { if (_model && _model->isActive()) { - // note: going through Rig because Model::getJointNames() (which proxies to FBXGeometry) was always empty + // note: going through Rig because Model::getJointNames() (which proxies to HFMGeometry) was always empty const Rig* rig = &(_model->getRig()); return mapJoints([rig](int jointIndex) -> QString { return rig->nameOfJoint(jointIndex); @@ -574,7 +574,7 @@ void ModelOverlay::animate() { QVector jointsData; - const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy + const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy int frameCount = frames.size(); if (frameCount <= 0) { return; @@ -606,10 +606,10 @@ void ModelOverlay::animate() { } QStringList animationJointNames = _animation->getGeometry().getJointNames(); - auto& fbxJoints = _animation->getGeometry().joints; + auto& hfmJoints = _animation->getGeometry().joints; - auto& originalFbxJoints = _model->getFBXGeometry().joints; - auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; + auto& originalHFMJoints = _model->getHFMGeometry().joints; + auto& originalFbxIndices = _model->getHFMGeometry().jointIndices; const QVector& rotations = frames[_lastKnownCurrentFrame].rotations; const QVector& translations = frames[_lastKnownCurrentFrame].translations; @@ -626,23 +626,23 @@ void ModelOverlay::animate() { translationMat = glm::translate(translations[index]); } } else if (index < animationJointNames.size()) { - QString jointName = fbxJoints[index].name; + QString jointName = hfmJoints[index].name; if (originalFbxIndices.contains(jointName)) { // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get its translation. int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. - translationMat = glm::translate(originalFbxJoints[remappedIndex].translation); + translationMat = glm::translate(originalHFMJoints[remappedIndex].translation); } } glm::mat4 rotationMat; if (index < rotations.size()) { - rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); + rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * rotations[index] * hfmJoints[index].postRotation); } else { - rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation); + rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * hfmJoints[index].postRotation); } - glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform * - rotationMat * fbxJoints[index].postTransform); + glm::mat4 finalMat = (translationMat * hfmJoints[index].preTransform * + rotationMat * hfmJoints[index].postTransform); auto& jointData = jointsData[j]; jointData.translation = extractTranslation(finalMat); jointData.translationIsDefaultPose = false; diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index f9195a608b..d630218165 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -101,7 +101,7 @@ void AnimClip::copyFromNetworkAnim() { // build a mapping from animation joint indices to skeleton joint indices. // by matching joints with the same name. - const FBXGeometry& geom = _networkAnim->getGeometry(); + const HFMGeometry& geom = _networkAnim->getGeometry(); AnimSkeleton animSkeleton(geom); const auto animJointCount = animSkeleton.getNumJoints(); const auto skeletonJointCount = _skeleton->getNumJoints(); @@ -120,7 +120,7 @@ void AnimClip::copyFromNetworkAnim() { for (int frame = 0; frame < frameCount; frame++) { - const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame]; + const HFMAnimationFrame& hfmAnimFrame = geom.animationFrames[frame]; // init all joints in animation to default pose // this will give us a resonable result for bones in the model skeleton but not in the animation. @@ -132,8 +132,8 @@ void AnimClip::copyFromNetworkAnim() { for (int animJoint = 0; animJoint < animJointCount; animJoint++) { int skeletonJoint = jointMap[animJoint]; - const glm::vec3& fbxAnimTrans = fbxAnimFrame.translations[animJoint]; - const glm::quat& fbxAnimRot = fbxAnimFrame.rotations[animJoint]; + const glm::vec3& hfmAnimTrans = hfmAnimFrame.translations[animJoint]; + const glm::quat& hfmAnimRot = hfmAnimFrame.rotations[animJoint]; // skip joints that are in the animation but not in the skeleton. if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) { @@ -146,19 +146,19 @@ void AnimClip::copyFromNetworkAnim() { preRot.scale() = glm::vec3(1.0f); postRot.scale() = glm::vec3(1.0f); - AnimPose rot(glm::vec3(1.0f), fbxAnimRot, glm::vec3()); + AnimPose rot(glm::vec3(1.0f), hfmAnimRot, glm::vec3()); // adjust translation offsets, so large translation animatons on the reference skeleton // will be adjusted when played on a skeleton with short limbs. - const glm::vec3& fbxZeroTrans = geom.animationFrames[0].translations[animJoint]; + const glm::vec3& hfmZeroTrans = geom.animationFrames[0].translations[animJoint]; const AnimPose& relDefaultPose = _skeleton->getRelativeDefaultPose(skeletonJoint); float boneLengthScale = 1.0f; const float EPSILON = 0.0001f; - if (fabsf(glm::length(fbxZeroTrans)) > EPSILON) { - boneLengthScale = glm::length(relDefaultPose.trans()) / glm::length(fbxZeroTrans); + if (fabsf(glm::length(hfmZeroTrans)) > EPSILON) { + boneLengthScale = glm::length(relDefaultPose.trans()) / glm::length(hfmZeroTrans); } - AnimPose trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans() + boneLengthScale * (fbxAnimTrans - fbxZeroTrans)); + AnimPose trans = AnimPose(glm::vec3(1.0f), glm::quat(), relDefaultPose.trans() + boneLengthScale * (hfmAnimTrans - hfmZeroTrans)); _anim[frame][skeletonJoint] = trans * preRot * rot * postRot; } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index bed9c590be..fc4114ac7b 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -16,17 +16,17 @@ #include "AnimationLogging.h" -AnimSkeleton::AnimSkeleton(const FBXGeometry& fbxGeometry) { +AnimSkeleton::AnimSkeleton(const HFMGeometry& geometry) { // convert to std::vector of joints - std::vector joints; - joints.reserve(fbxGeometry.joints.size()); - for (auto& joint : fbxGeometry.joints) { + std::vector joints; + joints.reserve(geometry.joints.size()); + for (auto& joint : geometry.joints) { joints.push_back(joint); } buildSkeletonFromJoints(joints); } -AnimSkeleton::AnimSkeleton(const std::vector& joints) { +AnimSkeleton::AnimSkeleton(const std::vector& joints) { buildSkeletonFromJoints(joints); } @@ -166,7 +166,7 @@ void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const { } } -void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) { +void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) { _joints = joints; _jointsSize = (int)joints.size(); // build a cache of bind poses @@ -177,7 +177,7 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) _relativePreRotationPoses.reserve(_jointsSize); _relativePostRotationPoses.reserve(_jointsSize); - // iterate over FBXJoints and extract the bind pose information. + // iterate over HFMJoints and extract the bind pose information. for (int i = 0; i < _jointsSize; i++) { // build pre and post transforms @@ -240,7 +240,7 @@ void AnimSkeleton::dump(bool verbose) const { qCDebug(animation) << " absDefaultPose =" << getAbsoluteDefaultPose(i); qCDebug(animation) << " relDefaultPose =" << getRelativeDefaultPose(i); if (verbose) { - qCDebug(animation) << " fbxJoint ="; + qCDebug(animation) << " hfmJoint ="; qCDebug(animation) << " isFree =" << _joints[i].isFree; qCDebug(animation) << " freeLineage =" << _joints[i].freeLineage; qCDebug(animation) << " parentIndex =" << _joints[i].parentIndex; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 2ebf3f4f5d..1717d75985 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -23,8 +23,8 @@ public: using Pointer = std::shared_ptr; using ConstPointer = std::shared_ptr; - explicit AnimSkeleton(const FBXGeometry& fbxGeometry); - explicit AnimSkeleton(const std::vector& joints); + explicit AnimSkeleton(const HFMGeometry& geometry); + explicit AnimSkeleton(const std::vector& joints); int nameToJointIndex(const QString& jointName) const; const QString& getJointName(int jointIndex) const; int getNumJoints() const; @@ -64,9 +64,9 @@ public: std::vector lookUpJointIndices(const std::vector& jointNames) const; protected: - void buildSkeletonFromJoints(const std::vector& joints); + void buildSkeletonFromJoints(const std::vector& joints); - std::vector _joints; + std::vector _joints; int _jointsSize { 0 }; AnimPoseVec _relativeDefaultPoses; AnimPoseVec _absoluteDefaultPoses; diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 04b7952ddb..b5b27c3a24 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -69,7 +69,7 @@ void AnimationReader::run() { if (urlValid) { // Parse the FBX directly from the QNetworkReply - FBXGeometry::Pointer fbxgeo; + HFMGeometry::Pointer fbxgeo; if (_url.path().toLower().endsWith(".fbx")) { fbxgeo.reset(readFBX(_data, QVariantHash(), _url.path())); } else { @@ -100,40 +100,40 @@ QStringList Animation::getJointNames() const { } QStringList names; if (_geometry) { - foreach (const FBXJoint& joint, _geometry->joints) { + foreach (const HFMJoint& joint, _geometry->joints) { names.append(joint.name); } } return names; } -QVector Animation::getFrames() const { +QVector Animation::getFrames() const { if (QThread::currentThread() != thread()) { - QVector result; + QVector result; BLOCKING_INVOKE_METHOD(const_cast(this), "getFrames", - Q_RETURN_ARG(QVector, result)); + Q_RETURN_ARG(QVector, result)); return result; } if (_geometry) { return _geometry->animationFrames; } else { - return QVector(); + return QVector(); } } -const QVector& Animation::getFramesReference() const { +const QVector& Animation::getFramesReference() const { return _geometry->animationFrames; } void Animation::downloadFinished(const QByteArray& data) { // parse the animation/fbx file on a background thread. AnimationReader* animationReader = new AnimationReader(_url, data); - connect(animationReader, SIGNAL(onSuccess(FBXGeometry::Pointer)), SLOT(animationParseSuccess(FBXGeometry::Pointer))); + connect(animationReader, SIGNAL(onSuccess(HFMGeometry::Pointer)), SLOT(animationParseSuccess(HFMGeometry::Pointer))); connect(animationReader, SIGNAL(onError(int, QString)), SLOT(animationParseError(int, QString))); QThreadPool::globalInstance()->start(animationReader); } -void Animation::animationParseSuccess(FBXGeometry::Pointer geometry) { +void Animation::animationParseSuccess(HFMGeometry::Pointer geometry) { qCDebug(animation) << "Animation parse success" << _url.toDisplayString(); diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index 483350e2b5..302f23a4e7 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -66,7 +66,7 @@ public: QString getType() const override { return "Animation"; } - const FBXGeometry& getGeometry() const { return *_geometry; } + const HFMGeometry& getGeometry() const { return *_geometry; } virtual bool isLoaded() const override; @@ -80,20 +80,20 @@ public: * @function AnimationObject.getFrames * @returns {FBXAnimationFrame[]} */ - Q_INVOKABLE QVector getFrames() const; + Q_INVOKABLE QVector getFrames() const; - const QVector& getFramesReference() const; + const QVector& getFramesReference() const; protected: virtual void downloadFinished(const QByteArray& data) override; protected slots: - void animationParseSuccess(FBXGeometry::Pointer geometry); + void animationParseSuccess(HFMGeometry::Pointer geometry); void animationParseError(int error, QString str); private: - FBXGeometry::Pointer _geometry; + HFMGeometry::Pointer _geometry; }; /// Reads geometry in a worker thread. @@ -105,7 +105,7 @@ public: virtual void run() override; signals: - void onSuccess(FBXGeometry::Pointer geometry); + void onSuccess(HFMGeometry::Pointer geometry); void onError(int error, QString str); private: diff --git a/libraries/animation/src/AnimationObject.cpp b/libraries/animation/src/AnimationObject.cpp index 7f0f35b104..bcbf497199 100644 --- a/libraries/animation/src/AnimationObject.cpp +++ b/libraries/animation/src/AnimationObject.cpp @@ -19,17 +19,17 @@ QStringList AnimationObject::getJointNames() const { return qscriptvalue_cast(thisObject())->getJointNames(); } -QVector AnimationObject::getFrames() const { +QVector AnimationObject::getFrames() const { return qscriptvalue_cast(thisObject())->getFrames(); } QVector AnimationFrameObject::getRotations() const { - return qscriptvalue_cast(thisObject()).rotations; + return qscriptvalue_cast(thisObject()).rotations; } void registerAnimationTypes(QScriptEngine* engine) { - qScriptRegisterSequenceMetaType >(engine); - engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( + qScriptRegisterSequenceMetaType >(engine); + engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( new AnimationFrameObject(), QScriptEngine::ScriptOwnership)); engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( new AnimationObject(), QScriptEngine::ScriptOwnership)); diff --git a/libraries/animation/src/AnimationObject.h b/libraries/animation/src/AnimationObject.h index aa69e78ceb..83880ed2ab 100644 --- a/libraries/animation/src/AnimationObject.h +++ b/libraries/animation/src/AnimationObject.h @@ -23,13 +23,13 @@ class QScriptEngine; class AnimationObject : public QObject, protected QScriptable { Q_OBJECT Q_PROPERTY(QStringList jointNames READ getJointNames) - Q_PROPERTY(QVector frames READ getFrames) + Q_PROPERTY(QVector frames READ getFrames) public: Q_INVOKABLE QStringList getJointNames() const; - Q_INVOKABLE QVector getFrames() const; + Q_INVOKABLE QVector getFrames() const; }; /// Scriptable wrapper for animation frames. diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 33b9569758..2641be92da 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -260,7 +260,7 @@ void Rig::destroyAnimGraph() { _rightEyeJointChildren.clear(); } -void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { +void Rig::initJointStates(const HFMGeometry& geometry, const glm::mat4& modelOffset) { _geometryOffset = AnimPose(geometry.offset); _invGeometryOffset = _geometryOffset.inverse(); _geometryToRigTransform = modelOffset * geometry.offset; @@ -307,7 +307,7 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(geometry.rightEyeJointIndex); } -void Rig::reset(const FBXGeometry& geometry) { +void Rig::reset(const HFMGeometry& geometry) { _geometryOffset = AnimPose(geometry.offset); _invGeometryOffset = _geometryOffset.inverse(); _animSkeleton = std::make_shared(geometry); @@ -1253,7 +1253,7 @@ const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = { // returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose. // if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward // such that it lies on the surface of the kdop. -static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const FBXJointShapeInfo& shapeInfo, glm::vec3& displacementOut) { +static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const HFMJointShapeInfo& shapeInfo, glm::vec3& displacementOut) { // transform point into local space of jointShape. glm::vec3 localPoint = shapePose.inverse().xformPoint(point); @@ -1299,8 +1299,8 @@ static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& sh } } -glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const { +glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, + const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const { glm::vec3 position = handPosition; glm::vec3 displacement; int hipsJoint = indexOfJoint("Hips"); @@ -1349,8 +1349,8 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJoin void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, - const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, + const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, + const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { const bool ENABLE_POLE_VECTORS = true; @@ -2008,7 +2008,7 @@ void Rig::computeExternalPoses(const glm::mat4& modelOffsetMat) { } void Rig::computeAvatarBoundingCapsule( - const FBXGeometry& geometry, + const HFMGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& localOffsetOut) const { @@ -2041,7 +2041,7 @@ void Rig::computeAvatarBoundingCapsule( // from the head to the hips when computing the rest of the bounding capsule. int index = indexOfJoint("Head"); while (index != -1) { - const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo; + const HFMJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo; AnimPose pose = _animSkeleton->getAbsoluteDefaultPose(index); if (shapeInfo.points.size() > 0) { for (auto& point : shapeInfo.points) { diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 7a090bd7bd..61e8672972 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -86,10 +86,10 @@ public: AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space uint8_t secondaryControllerFlags[NumSecondaryControllerTypes]; bool isTalking; - FBXJointShapeInfo hipsShapeInfo; - FBXJointShapeInfo spineShapeInfo; - FBXJointShapeInfo spine1ShapeInfo; - FBXJointShapeInfo spine2ShapeInfo; + HFMJointShapeInfo hipsShapeInfo; + HFMJointShapeInfo spineShapeInfo; + HFMJointShapeInfo spine1ShapeInfo; + HFMJointShapeInfo spine2ShapeInfo; }; struct EyeParameters { @@ -122,8 +122,8 @@ public: void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); void restoreRoleAnimation(const QString& role); - void initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset); - void reset(const FBXGeometry& geometry); + void initJointStates(const HFMGeometry& geometry, const glm::mat4& modelOffset); + void reset(const HFMGeometry& geometry); bool jointStatesEmpty(); int getJointStateCount() const; int indexOfJoint(const QString& jointName) const; @@ -210,7 +210,7 @@ public: void copyJointsFromJointData(const QVector& jointDataVec); void computeExternalPoses(const glm::mat4& modelOffsetMat); - void computeAvatarBoundingCapsule(const FBXGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const; + void computeAvatarBoundingCapsule(const HFMGeometry& geometry, float& radiusOut, float& heightOut, glm::vec3& offsetOut) const; void setEnableInverseKinematics(bool enable); void setEnableAnimations(bool enable); @@ -245,8 +245,8 @@ protected: void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, bool leftArmEnabled, bool rightArmEnabled, bool headEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, - const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, + const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, + const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo, const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); void updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, @@ -257,8 +257,8 @@ protected: bool calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const; glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; - glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const; + glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo, + const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const; AnimPose _modelOffset; // model to rig space diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 86f5bd69a5..b8448e389d 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1311,7 +1311,7 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { case CAMERA_MATRIX_INDEX: { glm::quat rotation; if (_skeletonModel && _skeletonModel->isActive()) { - int headJointIndex = _skeletonModel->getFBXGeometry().headJointIndex; + int headJointIndex = _skeletonModel->getHFMGeometry().headJointIndex; if (headJointIndex >= 0) { _skeletonModel->getAbsoluteJointRotationInRigFrame(headJointIndex, rotation); } @@ -1360,7 +1360,7 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { case CAMERA_MATRIX_INDEX: { glm::vec3 translation; if (_skeletonModel && _skeletonModel->isActive()) { - int headJointIndex = _skeletonModel->getFBXGeometry().headJointIndex; + int headJointIndex = _skeletonModel->getHFMGeometry().headJointIndex; if (headJointIndex >= 0) { _skeletonModel->getAbsoluteJointTranslationInRigFrame(headJointIndex, translation); } @@ -1416,7 +1416,7 @@ void Avatar::withValidJointIndicesCache(std::function const& worker) con if (!_modelJointsCached) { _modelJointIndicesCache.clear(); if (_skeletonModel && _skeletonModel->isActive()) { - _modelJointIndicesCache = _skeletonModel->getFBXGeometry().jointIndices; + _modelJointIndicesCache = _skeletonModel->getHFMGeometry().jointIndices; _modelJointsCached = true; } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 1ec58fd704..a41cff528b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -54,7 +54,7 @@ void SkeletonModel::setTextures(const QVariantMap& textures) { } void SkeletonModel::initJointStates() { - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig.initJointStates(geometry, modelOffset); @@ -96,7 +96,7 @@ void SkeletonModel::initJointStates() { // Called within Model::simulate call, below. void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { assert(!_owningAvatar->isMyAvatar()); - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); Head* head = _owningAvatar->getHead(); @@ -259,22 +259,22 @@ bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const { } bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const { - return isActive() && getJointPositionInWorldFrame(getFBXGeometry().headJointIndex, headPosition); + return isActive() && getJointPositionInWorldFrame(getHFMGeometry().headJointIndex, headPosition); } bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const { - return isActive() && getJointPositionInWorldFrame(getFBXGeometry().neckJointIndex, neckPosition); + return isActive() && getJointPositionInWorldFrame(getHFMGeometry().neckJointIndex, neckPosition); } bool SkeletonModel::getLocalNeckPosition(glm::vec3& neckPosition) const { - return isActive() && getJointPosition(getFBXGeometry().neckJointIndex, neckPosition); + return isActive() && getJointPosition(getHFMGeometry().neckJointIndex, neckPosition); } bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (!isActive()) { return false; } - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); if (getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) && getJointPosition(geometry.rightEyeJointIndex, secondEyePosition)) { @@ -330,7 +330,7 @@ void SkeletonModel::computeBoundingShape() { return; } - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); if (geometry.joints.isEmpty() || geometry.rootJointIndex == -1) { // rootJointIndex == -1 if the avatar model has no skeleton return; @@ -369,7 +369,7 @@ void SkeletonModel::renderBoundingCollisionShapes(RenderArgs* args, gpu::Batch& } bool SkeletonModel::hasSkeleton() { - return isActive() ? getFBXGeometry().rootJointIndex != -1 : false; + return isActive() ? getHFMGeometry().rootJointIndex != -1 : false; } void SkeletonModel::onInvalidate() { diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index d82fce7412..6c533a5941 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -41,10 +41,10 @@ public: void updateAttitude(const glm::quat& orientation); /// Returns the index of the left hand joint, or -1 if not found. - int getLeftHandJointIndex() const { return isActive() ? getFBXGeometry().leftHandJointIndex : -1; } + int getLeftHandJointIndex() const { return isActive() ? getHFMGeometry().leftHandJointIndex : -1; } /// Returns the index of the right hand joint, or -1 if not found. - int getRightHandJointIndex() const { return isActive() ? getFBXGeometry().rightHandJointIndex : -1; } + int getRightHandJointIndex() const { return isActive() ? getHFMGeometry().rightHandJointIndex : -1; } bool getLeftGrabPosition(glm::vec3& position) const; bool getRightGrabPosition(glm::vec3& position) const; diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index b90082d969..0b76c275d4 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -206,7 +206,7 @@ void FBXBaker::importScene() { } #endif - _geometry = reader.extractFBXGeometry({}, _modelURL.toString()); + _geometry = reader.extractHFMGeometry({}, _modelURL.toString()); _textureContentMap = reader._textureContent; } @@ -329,7 +329,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { for (FBXNode& textureChild : object->children) { if (textureChild.name == "RelativeFilename") { - QString fbxTextureFileName { textureChild.properties.at(0).toString() }; + QString hfmTextureFileName { textureChild.properties.at(0).toString() }; // grab the ID for this texture so we can figure out the // texture type from the loaded materials @@ -337,7 +337,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { auto textureType = textureTypes[textureID]; // Compress the texture information and return the new filename to be added into the FBX scene - auto bakedTextureFile = compressTexture(fbxTextureFileName, textureType); + auto bakedTextureFile = compressTexture(hfmTextureFileName, textureType); // If no errors or warnings have occurred during texture compression add the filename to the FBX scene if (!bakedTextureFile.isNull()) { diff --git a/libraries/baking/src/FBXBaker.h b/libraries/baking/src/FBXBaker.h index 9d41209d4c..8edaf91c79 100644 --- a/libraries/baking/src/FBXBaker.h +++ b/libraries/baking/src/FBXBaker.h @@ -53,7 +53,7 @@ private: void rewriteAndBakeSceneModels(); void rewriteAndBakeSceneTextures(); - FBXGeometry* _geometry; + HFMGeometry* _geometry; QHash _textureNameMatchCount; QHash _remappedTexturePaths; diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp index 75e10c54ab..ca352cebae 100644 --- a/libraries/baking/src/ModelBaker.cpp +++ b/libraries/baking/src/ModelBaker.cpp @@ -75,7 +75,7 @@ void ModelBaker::abort() { } } -bool ModelBaker::compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback) { +bool ModelBaker::compressMesh(HFMMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback) { if (mesh.wasCompressed) { handleError("Cannot re-bake a file that contains compressed mesh"); return false; diff --git a/libraries/baking/src/ModelBaker.h b/libraries/baking/src/ModelBaker.h index 1fd77ab761..cda4478b1d 100644 --- a/libraries/baking/src/ModelBaker.h +++ b/libraries/baking/src/ModelBaker.h @@ -39,7 +39,7 @@ public: const QString& bakedOutputDirectory, const QString& originalOutputDirectory = ""); virtual ~ModelBaker(); - bool compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback = nullptr); + bool compressMesh(HFMMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback = nullptr); QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE); virtual void setWasAborted(bool wasAborted) override; diff --git a/libraries/baking/src/OBJBaker.cpp b/libraries/baking/src/OBJBaker.cpp index cf62bc4fa8..e9130e3fbd 100644 --- a/libraries/baking/src/OBJBaker.cpp +++ b/libraries/baking/src/OBJBaker.cpp @@ -153,7 +153,7 @@ void OBJBaker::bakeOBJ() { checkIfTexturesFinished(); } -void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { +void OBJBaker::createFBXNodeTree(FBXNode& rootNode, HFMGeometry& geometry) { // Generating FBX Header Node FBXNode headerNode; headerNode.name = FBX_HEADER_EXTENSION; @@ -235,7 +235,7 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { auto size = meshParts.size(); for (int i = 0; i < size; i++) { QString material = meshParts[i].materialID; - FBXMaterial currentMaterial = geometry.materials[material]; + HFMMaterial currentMaterial = geometry.materials[material]; if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) { auto textureID = nextNodeID(); _mapTextureMaterial.emplace_back(textureID, i); @@ -325,12 +325,12 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) { } // Set properties for material nodes -void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry) { +void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, HFMGeometry& geometry) { auto materialID = nextNodeID(); _materialIDs.push_back(materialID); materialNode.properties = { materialID, material, MESH }; - FBXMaterial currentMaterial = geometry.materials[material]; + HFMMaterial currentMaterial = geometry.materials[material]; // Setting the hierarchy: Material -> Properties70 -> P -> Properties FBXNode properties70Node; diff --git a/libraries/baking/src/OBJBaker.h b/libraries/baking/src/OBJBaker.h index 8e49692d35..875a500129 100644 --- a/libraries/baking/src/OBJBaker.h +++ b/libraries/baking/src/OBJBaker.h @@ -39,8 +39,8 @@ private slots: private: void loadOBJ(); - void createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry); - void setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry); + void createFBXNodeTree(FBXNode& rootNode, HFMGeometry& geometry); + void setMaterialNodeProperties(FBXNode& materialNode, QString material, HFMGeometry& geometry); NodeID nextNodeID() { return _nodeID++; } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c6337dc872..c36f60600f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -268,7 +268,7 @@ EntityItemProperties RenderableModelEntityItem::getProperties(const EntityProper if (model->isLoaded()) { // TODO: improve naturalDimensions in the future, // for now we've added this hack for setting natural dimensions of models - Extents meshExtents = model->getFBXGeometry().getUnscaledMeshExtents(); + Extents meshExtents = model->getHFMGeometry().getUnscaledMeshExtents(); properties.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum); properties.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum); } @@ -403,7 +403,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // should never fall in here when collision model not fully loaded // TODO: assert that all geometries exist and are loaded //assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); - const FBXGeometry& collisionGeometry = _compoundShapeResource->getFBXGeometry(); + const HFMGeometry& collisionGeometry = _compoundShapeResource->getHFMGeometry(); ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); pointCollection.clear(); @@ -411,15 +411,15 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. - foreach (const FBXMesh& mesh, collisionGeometry.meshes) { + foreach (const HFMMesh& mesh, collisionGeometry.meshes) { // each meshPart is a convex hull - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { pointCollection.push_back(QVector()); ShapeInfo::PointList& pointsInPart = pointCollection[i]; // run through all the triangles and (uniquely) add each point to the hull uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -440,7 +440,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // run through all the quads and (uniquely) add each point to the hull numIndices = (uint32_t)meshPart.quadIndices.size(); - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % QUAD_STRIDE == 0); numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -478,7 +478,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // to the visual model and apply them to the collision model (without regard for the // collision model's extents). - glm::vec3 scaleToFit = dimensions / model->getFBXGeometry().getUnscaledMeshExtents().size(); + glm::vec3 scaleToFit = dimensions / model->getHFMGeometry().getUnscaledMeshExtents().size(); // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()); @@ -498,14 +498,14 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // compute meshPart local transforms QVector localTransforms; - const FBXGeometry& fbxGeometry = model->getFBXGeometry(); - int numFbxMeshes = fbxGeometry.meshes.size(); + const HFMGeometry& hfmGeometry = model->getHFMGeometry(); + int numHFMMeshes = hfmGeometry.meshes.size(); int totalNumVertices = 0; glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT)); - for (int i = 0; i < numFbxMeshes; i++) { - const FBXMesh& mesh = fbxGeometry.meshes.at(i); + for (int i = 0; i < numHFMMeshes; i++) { + const HFMMesh& mesh = hfmGeometry.meshes.at(i); if (mesh.clusters.size() > 0) { - const FBXCluster& cluster = mesh.clusters.at(0); + const HFMCluster& cluster = mesh.clusters.at(0); auto jointMatrix = model->getRig().getJointTransform(cluster.jointIndex); // we backtranslate by the registration offset so we can apply that offset to the shapeInfo later localTransforms.push_back(invRegistraionOffset * jointMatrix * cluster.inverseBindMatrix); @@ -524,10 +524,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { std::vector> meshes; if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { - auto& fbxMeshes = _compoundShapeResource->getFBXGeometry().meshes; - meshes.reserve(fbxMeshes.size()); - for (auto& fbxMesh : fbxMeshes) { - meshes.push_back(fbxMesh._mesh); + auto& hfmMeshes = _compoundShapeResource->getHFMGeometry().meshes; + meshes.reserve(hfmMeshes.size()); + for (auto& hfmMesh : hfmMeshes) { + meshes.push_back(hfmMesh._mesh); } } else { meshes = model->getGeometry()->getMeshes(); @@ -594,7 +594,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { while (partItr != parts.cend()) { auto numIndices = partItr->_numIndices; if (partItr->_topology == graphics::Mesh::TRIANGLES) { - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices % TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -605,7 +605,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { ++indexItr; } } else if (partItr->_topology == graphics::Mesh::TRIANGLE_STRIP) { - // TODO: resurrect assert after we start sanitizing FBXMesh higher up + // TODO: resurrect assert after we start sanitizing HFMMesh higher up //assert(numIndices > 2); uint32_t approxNumIndices = TRIANGLE_STRIDE * numIndices; @@ -651,7 +651,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { std::set uniqueIndices; auto numIndices = partItr->_numIndices; if (partItr->_topology == graphics::Mesh::TRIANGLES) { - // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + // TODO: assert rather than workaround after we start sanitizing HFMMesh higher up //assert(numIndices% TRIANGLE_STRIDE == 0); numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader @@ -662,7 +662,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { ++indexItr; } } else if (partItr->_topology == graphics::Mesh::TRIANGLE_STRIP) { - // TODO: resurrect assert after we start sanitizing FBXMesh higher up + // TODO: resurrect assert after we start sanitizing HFMMesh higher up //assert(numIndices > TRIANGLE_STRIDE - 1); auto indexItr = indices.cbegin() + partItr->_startIndex; @@ -755,7 +755,7 @@ int RenderableModelEntityItem::avatarJointIndex(int modelJointIndex) { bool RenderableModelEntityItem::contains(const glm::vec3& point) const { auto model = getModel(); if (EntityItem::contains(point) && model && _compoundShapeResource && _compoundShapeResource->isLoaded()) { - return _compoundShapeResource->getFBXGeometry().convexHullContains(worldToEntity(point)); + return _compoundShapeResource->getHFMGeometry().convexHullContains(worldToEntity(point)); } return false; @@ -1135,7 +1135,7 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { QVector jointsData; - const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy + const QVector& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy int frameCount = frames.size(); if (frameCount <= 0) { return; @@ -1160,10 +1160,10 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { } QStringList animationJointNames = _animation->getGeometry().getJointNames(); - auto& fbxJoints = _animation->getGeometry().joints; + auto& hfmJoints = _animation->getGeometry().joints; - auto& originalFbxJoints = _model->getFBXGeometry().joints; - auto& originalFbxIndices = _model->getFBXGeometry().jointIndices; + auto& originalHFMJoints = _model->getHFMGeometry().joints; + auto& originalHFMIndices = _model->getHFMGeometry().jointIndices; bool allowTranslation = entity->getAnimationAllowTranslation(); @@ -1182,22 +1182,22 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { translationMat = glm::translate(translations[index]); } } else if (index < animationJointNames.size()) { - QString jointName = fbxJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation - if (originalFbxIndices.contains(jointName)) { + QString jointName = hfmJoints[index].name; // Pushing this here so its not done on every entity, with the exceptions of those allowing for translation + if (originalHFMIndices.contains(jointName)) { // Making sure the joint names exist in the original model the animation is trying to apply onto. If they do, then remap and get it's translation. - int remappedIndex = originalFbxIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. - translationMat = glm::translate(originalFbxJoints[remappedIndex].translation); + int remappedIndex = originalHFMIndices[jointName] - 1; // JointIndeces seem to always start from 1 and the found index is always 1 higher than actual. + translationMat = glm::translate(originalHFMJoints[remappedIndex].translation); } } glm::mat4 rotationMat; if (index < rotations.size()) { - rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); + rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * rotations[index] * hfmJoints[index].postRotation); } else { - rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation); + rotationMat = glm::mat4_cast(hfmJoints[index].preRotation * hfmJoints[index].postRotation); } - glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform * - rotationMat * fbxJoints[index].postTransform); + glm::mat4 finalMat = (translationMat * hfmJoints[index].preTransform * + rotationMat * hfmJoints[index].postTransform); auto& jointData = jointsData[j]; jointData.translation = extractTranslation(finalMat); jointData.translationSet = true; diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index fdebb16bc8..8051dbafea 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -70,8 +70,8 @@ public: }; -/// A single blendshape extracted from an FBX document. -class FBXBlendshape { +/// A single blendshape. +class HFMBlendshape { public: QVector indices; QVector vertices; @@ -79,19 +79,19 @@ public: QVector tangents; }; -struct FBXJointShapeInfo { - // same units and frame as FBXJoint.translation +struct HFMJointShapeInfo { + // same units and frame as HFMJoint.translation glm::vec3 avgPoint; std::vector dots; std::vector points; std::vector debugLines; }; -/// A single joint (transformation node) extracted from an FBX document. -class FBXJoint { +/// A single joint (transformation node). +class HFMJoint { public: - FBXJointShapeInfo shapeInfo; + HFMJointShapeInfo shapeInfo; QVector freeLineage; bool isFree; int parentIndex; @@ -126,8 +126,8 @@ public: }; -/// A single binding to a joint in an FBX document. -class FBXCluster { +/// A single binding to a joint. +class HFMCluster { public: int jointIndex; @@ -137,8 +137,8 @@ public: const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048; -/// A texture map in an FBX document. -class FBXTexture { +/// A texture map. +class HFMTexture { public: QString id; QString name; @@ -156,7 +156,7 @@ public: }; /// A single part of a mesh (with the same material). -class FBXMeshPart { +class HFMMeshPart { public: QVector quadIndices; // original indices from the FBX mesh @@ -166,10 +166,10 @@ public: QString materialID; }; -class FBXMaterial { +class HFMMaterial { public: - FBXMaterial() {}; - FBXMaterial(const glm::vec3& diffuseColor, const glm::vec3& specularColor, const glm::vec3& emissiveColor, + HFMMaterial() {}; + HFMMaterial(const glm::vec3& diffuseColor, const glm::vec3& specularColor, const glm::vec3& emissiveColor, float shininess, float opacity) : diffuseColor(diffuseColor), specularColor(specularColor), @@ -203,17 +203,17 @@ public: QString shadingModel; graphics::MaterialPointer _material; - FBXTexture normalTexture; - FBXTexture albedoTexture; - FBXTexture opacityTexture; - FBXTexture glossTexture; - FBXTexture roughnessTexture; - FBXTexture specularTexture; - FBXTexture metallicTexture; - FBXTexture emissiveTexture; - FBXTexture occlusionTexture; - FBXTexture scatteringTexture; - FBXTexture lightmapTexture; + HFMTexture normalTexture; + HFMTexture albedoTexture; + HFMTexture opacityTexture; + HFMTexture glossTexture; + HFMTexture roughnessTexture; + HFMTexture specularTexture; + HFMTexture metallicTexture; + HFMTexture emissiveTexture; + HFMTexture occlusionTexture; + HFMTexture scatteringTexture; + HFMTexture lightmapTexture; glm::vec2 lightmapParams{ 0.0f, 1.0f }; @@ -232,10 +232,10 @@ public: }; /// A single mesh (with optional blendshapes) extracted from an FBX document. -class FBXMesh { +class HFMMesh { public: - QVector parts; + QVector parts; QVector vertices; QVector normals; @@ -247,12 +247,12 @@ public: QVector clusterWeights; QVector originalIndices; - QVector clusters; + QVector clusters; Extents meshExtents; glm::mat4 modelTransform; - QVector blendshapes; + QVector blendshapes; unsigned int meshIndex; // the order the meshes appeared in the object file @@ -265,7 +265,7 @@ public: class ExtractedMesh { public: - FBXMesh mesh; + HFMMesh mesh; QMultiHash newIndices; QVector > blendshapeIndexMaps; QVector > partMaterialTextures; @@ -278,14 +278,14 @@ public: * @property {Vec3[]} translations */ /// A single animation frame extracted from an FBX document. -class FBXAnimationFrame { +class HFMAnimationFrame { public: QVector rotations; QVector translations; }; -/// A light in an FBX document. -class FBXLight { +/// A light. +class HFMLight { public: QString name; Transform transform; @@ -293,7 +293,7 @@ public: float fogValue; glm::vec3 color; - FBXLight() : + HFMLight() : name(), transform(), intensity(1.0f), @@ -302,26 +302,26 @@ public: {} }; -Q_DECLARE_METATYPE(FBXAnimationFrame) -Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(HFMAnimationFrame) +Q_DECLARE_METATYPE(QVector) /// A set of meshes extracted from an FBX document. -class FBXGeometry { +class HFMGeometry { public: - using Pointer = std::shared_ptr; + using Pointer = std::shared_ptr; QString originalURL; QString author; QString applicationName; ///< the name of the application that generated the model - QVector joints; + QVector joints; QHash jointIndices; ///< 1-based, so as to more easily detect missing indices bool hasSkeletonJoints; - QVector meshes; + QVector meshes; QVector scripts; - QHash materials; + QHash materials; glm::mat4 offset; // This includes offset, rotation, and scale as specified by the FST file @@ -348,7 +348,7 @@ public: Extents bindExtents; Extents meshExtents; - QVector animationFrames; + QVector animationFrames; int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; } QStringList getJointNames() const; @@ -368,7 +368,7 @@ public: QList blendshapeChannelNames; }; -Q_DECLARE_METATYPE(FBXGeometry) -Q_DECLARE_METATYPE(FBXGeometry::Pointer) +Q_DECLARE_METATYPE(HFMGeometry) +Q_DECLARE_METATYPE(HFMGeometry::Pointer) #endif // hifi_FBX_h_ diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index dd766f002c..df6abbfdf2 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -40,19 +40,19 @@ using namespace std; -int FBXGeometryPointerMetaTypeId = qRegisterMetaType(); +int HFMGeometryPointerMetaTypeId = qRegisterMetaType(); -QStringList FBXGeometry::getJointNames() const { +QStringList HFMGeometry::getJointNames() const { QStringList names; - foreach (const FBXJoint& joint, joints) { + foreach (const HFMJoint& joint, joints) { names.append(joint.name); } return names; } -bool FBXGeometry::hasBlendedMeshes() const { +bool HFMGeometry::hasBlendedMeshes() const { if (!meshes.isEmpty()) { - foreach (const FBXMesh& mesh, meshes) { + foreach (const HFMMesh& mesh, meshes) { if (!mesh.blendshapes.isEmpty()) { return true; } @@ -61,7 +61,7 @@ bool FBXGeometry::hasBlendedMeshes() const { return false; } -Extents FBXGeometry::getUnscaledMeshExtents() const { +Extents HFMGeometry::getUnscaledMeshExtents() const { const Extents& extents = meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which @@ -74,12 +74,12 @@ Extents FBXGeometry::getUnscaledMeshExtents() const { } // TODO: Move to graphics::Mesh when Sam's ready -bool FBXGeometry::convexHullContains(const glm::vec3& point) const { +bool HFMGeometry::convexHullContains(const glm::vec3& point) const { if (!getUnscaledMeshExtents().containsPoint(point)) { return false; } - auto checkEachPrimitive = [=](FBXMesh& mesh, QVector indices, int primitiveSize) -> bool { + auto checkEachPrimitive = [=](HFMMesh& mesh, QVector indices, int primitiveSize) -> bool { // Check whether the point is "behind" all the primitives. int verticesSize = mesh.vertices.size(); for (int j = 0; @@ -124,16 +124,16 @@ bool FBXGeometry::convexHullContains(const glm::vec3& point) const { return false; } -QString FBXGeometry::getModelNameOfMesh(int meshIndex) const { +QString HFMGeometry::getModelNameOfMesh(int meshIndex) const { if (meshIndicesToModelNames.contains(meshIndex)) { return meshIndicesToModelNames.value(meshIndex); } return QString(); } -int fbxGeometryMetaTypeId = qRegisterMetaType(); -int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); -int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); +int hfmGeometryMetaTypeId = qRegisterMetaType(); +int hfmAnimationFrameMetaTypeId = qRegisterMetaType(); +int hfmAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); glm::vec3 parseVec3(const QString& string) { @@ -303,7 +303,7 @@ glm::mat4 getGlobalTransform(const QMultiMap& _connectionParen class ExtractedBlendshape { public: QString id; - FBXBlendshape blendshape; + HFMBlendshape blendshape; }; void printNode(const FBXNode& node, int indentLevel) { @@ -346,8 +346,8 @@ void appendModelIDs(const QString& parentID, const QMultiMap& } } -FBXBlendshape extractBlendshape(const FBXNode& object) { - FBXBlendshape blendshape; +HFMBlendshape extractBlendshape(const FBXNode& object) { + HFMBlendshape blendshape; foreach (const FBXNode& data, object.children) { if (data.name == "Indexes") { blendshape.indices = FBXReader::getIntVector(data); @@ -362,9 +362,9 @@ FBXBlendshape extractBlendshape(const FBXNode& object) { return blendshape; } -using IndexAccessor = std::function; +using IndexAccessor = std::function; -static void setTangents(const FBXMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex, +static void setTangents(const HFMMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex, const QVector& vertices, const QVector& normals, QVector& tangents) { glm::vec3 vertex[2]; glm::vec3 normal; @@ -381,14 +381,14 @@ static void setTangents(const FBXMesh& mesh, const IndexAccessor& vertexAccessor } } -static void createTangents(const FBXMesh& mesh, bool generateFromTexCoords, +static void createTangents(const HFMMesh& mesh, bool generateFromTexCoords, const QVector& vertices, const QVector& normals, QVector& tangents, IndexAccessor accessor) { // if we have a normal map (and texture coordinates), we must compute tangents if (generateFromTexCoords && !mesh.texCoords.isEmpty()) { tangents.resize(vertices.size()); - foreach(const FBXMeshPart& part, mesh.parts) { + foreach(const HFMMeshPart& part, mesh.parts) { for (int i = 0; i < part.quadIndices.size(); i += 4) { setTangents(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1), vertices, normals, tangents); setTangents(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2), vertices, normals, tangents); @@ -403,27 +403,27 @@ static void createTangents(const FBXMesh& mesh, bool generateFromTexCoords, setTangents(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i), vertices, normals, tangents); } if ((part.triangleIndices.size() % 3) != 0) { - qCDebug(modelformat) << "Error in extractFBXGeometry part.triangleIndices.size() is not divisible by three "; + qCDebug(modelformat) << "Error in extractHFMGeometry part.triangleIndices.size() is not divisible by three "; } } } } -static void _createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, FBXBlendshape& blendShape); +static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape); -void FBXMesh::createBlendShapeTangents(bool generateTangents) { +void HFMMesh::createBlendShapeTangents(bool generateTangents) { for (auto& blendShape : blendshapes) { _createBlendShapeTangents(*this, generateTangents, blendShape); } } -void FBXMesh::createMeshTangents(bool generateFromTexCoords) { - FBXMesh& mesh = *this; +void HFMMesh::createMeshTangents(bool generateFromTexCoords) { + HFMMesh& mesh = *this; // This is the only workaround I've found to trick the compiler into understanding that mesh.tangents isn't // const in the lambda function. auto& tangents = mesh.tangents; createTangents(mesh, generateFromTexCoords, mesh.vertices, mesh.normals, mesh.tangents, - [&](const FBXMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { + [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { outVertices[0] = mesh.vertices[firstIndex]; outVertices[1] = mesh.vertices[secondIndex]; outNormal = mesh.normals[firstIndex]; @@ -431,7 +431,7 @@ void FBXMesh::createMeshTangents(bool generateFromTexCoords) { }); } -static void _createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, FBXBlendshape& blendShape) { +static void _createBlendShapeTangents(HFMMesh& mesh, bool generateFromTexCoords, HFMBlendshape& blendShape) { // Create lookup to get index in blend shape from vertex index in mesh std::vector reverseIndices; reverseIndices.resize(mesh.vertices.size()); @@ -443,7 +443,7 @@ static void _createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, } createTangents(mesh, generateFromTexCoords, blendShape.vertices, blendShape.normals, blendShape.tangents, - [&](const FBXMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { + [&](const HFMMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { const auto index1 = reverseIndices[firstIndex]; const auto index2 = reverseIndices[secondIndex]; @@ -481,7 +481,7 @@ void addBlendshapes(const ExtractedBlendshape& extracted, const QList& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first]; for (int i = 0; i < extracted.blendshape.indices.size(); i++) { int oldIndex = extracted.blendshape.indices.at(i); @@ -539,7 +539,7 @@ public: QVector values; }; -bool checkMaterialsHaveTextures(const QHash& materials, +bool checkMaterialsHaveTextures(const QHash& materials, const QHash& textureFilenames, const QMultiMap& _connectionChildMap) { foreach (const QString& materialID, materials.keys()) { foreach (const QString& childID, _connectionChildMap.values(materialID)) { @@ -569,8 +569,8 @@ int matchTextureUVSetToAttributeChannel(const QString& texUVSetName, const QHash } -FBXLight extractLight(const FBXNode& object) { - FBXLight light; +HFMLight extractLight(const FBXNode& object) { + HFMLight light; foreach (const FBXNode& subobject, object.children) { QString childname = QString(subobject.name); if (subobject.name == "Properties70") { @@ -615,7 +615,7 @@ QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { return filepath.mid(filepath.lastIndexOf('/') + 1); } -FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { +HFMGeometry* FBXReader::extractHFMGeometry(const QVariantHash& mapping, const QString& url) { const FBXNode& node = _rootNode; QMap meshes; QHash modelIDsToNames; @@ -636,7 +636,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QHash yComponents; QHash zComponents; - std::map lights; + std::map lights; QVariantHash joints = mapping.value("joint").toHash(); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); @@ -689,8 +689,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS #if defined(DEBUG_FBXREADER) int unknown = 0; #endif - FBXGeometry* geometryPtr = new FBXGeometry; - FBXGeometry& geometry = *geometryPtr; + HFMGeometry* geometryPtr = new HFMGeometry; + HFMGeometry& geometry = *geometryPtr; geometry.originalURL = url; @@ -944,7 +944,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS lightprop = vprop.toString(); } - FBXLight light = extractLight(object); + HFMLight light = extractLight(object); } } } else { @@ -1102,7 +1102,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS _textureContent.insert(filepath, content); } } else if (object.name == "Material") { - FBXMaterial material; + HFMMaterial material; material.name = (object.properties.at(1).toString()); foreach (const FBXNode& subobject, object.children) { bool properties = false; @@ -1255,7 +1255,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS #endif } material.materialID = getID(object.properties); - _fbxMaterials.insert(material.materialID, material); + _hfmMaterials.insert(material.materialID, material); } else if (object.name == "NodeAttribute") { @@ -1276,7 +1276,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (!attributetype.isEmpty()) { if (attributetype == "Light") { - FBXLight light = extractLight(object); + HFMLight light = extractLight(object); lights[attribID] = light; } } @@ -1345,7 +1345,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QString parentID = getID(connection.properties, 2); ooChildToParent.insert(childID, parentID); if (!hifiGlobalNodeID.isEmpty() && (parentID == hifiGlobalNodeID)) { - std::map< QString, FBXLight >::iterator lightIt = lights.find(childID); + std::map< QString, HFMLight >::iterator lightIt = lights.find(childID); if (lightIt != lights.end()) { _lightmapLevel = (*lightIt).second.intensity; if (_lightmapLevel <= 0.0f) { @@ -1504,7 +1504,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS frameCount = qMax(frameCount, curve.values.size()); } for (int i = 0; i < frameCount; i++) { - FBXAnimationFrame frame; + HFMAnimationFrame frame; frame.rotations.resize(modelIDs.size()); frame.translations.resize(modelIDs.size()); geometry.animationFrames.append(frame); @@ -1515,7 +1515,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS geometry.hasSkeletonJoints = false; foreach (const QString& modelID, modelIDs) { const FBXModel& model = models[modelID]; - FBXJoint joint; + HFMJoint joint; joint.isFree = freeJoints.contains(model.name); joint.parentIndex = model.parentIndex; @@ -1553,7 +1553,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS joint.distanceToParent = 0.0f; } else { - const FBXJoint& parentJoint = geometry.joints.at(joint.parentIndex); + const HFMJoint& parentJoint = geometry.joints.at(joint.parentIndex); joint.transform = parentJoint.transform * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; joint.inverseDefaultRotation = glm::inverse(combinedRotation) * parentJoint.inverseDefaultRotation; @@ -1631,7 +1631,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS geometry.meshExtents.reset(); // Create the Material Library - consolidateFBXMaterials(mapping); + consolidateHFMMaterials(mapping); // We can't allow the scaling of a given image to different sizes, because the hash used for the KTX cache is based on the original image // Allowing scaling of the same image to different sizes would cause different KTX files to target the same cache key @@ -1643,7 +1643,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS // 33 - 128 textures --> 512 // etc... QSet uniqueTextures; - for (auto& material : _fbxMaterials) { + for (auto& material : _hfmMaterials) { material.getTextureNames(uniqueTextures); } int numTextures = uniqueTextures.size(); @@ -1659,15 +1659,15 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } while (numTextureThreshold < numTextures && maxWidth > MIN_MIP_TEXTURE_WIDTH); qCDebug(modelformat) << "Capped square texture width =" << maxWidth << "for model" << url << "with" << numTextures << "textures"; - for (auto& material : _fbxMaterials) { + for (auto& material : _hfmMaterials) { material.setMaxNumPixelsPerTexture(maxWidth * maxWidth); } } #endif - geometry.materials = _fbxMaterials; + geometry.materials = _hfmMaterials; // see if any materials have texture children - bool materialsHaveTextures = checkMaterialsHaveTextures(_fbxMaterials, _textureFilenames, _connectionChildMap); + bool materialsHaveTextures = checkMaterialsHaveTextures(_hfmMaterials, _textureFilenames, _connectionChildMap); for (QMap::iterator it = meshes.begin(); it != meshes.end(); it++) { ExtractedMesh& extracted = it.value(); @@ -1698,13 +1698,13 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS for (int i = children.size() - 1; i >= 0; i--) { const QString& childID = children.at(i); - if (_fbxMaterials.contains(childID)) { + if (_hfmMaterials.contains(childID)) { // the pure material associated with this part - FBXMaterial material = _fbxMaterials.value(childID); + HFMMaterial material = _hfmMaterials.value(childID); for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { if (extracted.partMaterialTextures.at(j).first == materialIndex) { - FBXMeshPart& part = extracted.mesh.parts[j]; + HFMMeshPart& part = extracted.mesh.parts[j]; part.materialID = material.materialID; generateTangents |= material.needTangentSpace(); } @@ -1713,7 +1713,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS materialIndex++; } else if (_textureFilenames.contains(childID)) { - FBXTexture texture = getTexture(childID); + HFMTexture texture = getTexture(childID); for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { int partTexture = extracted.partMaterialTextures.at(j).second; if (partTexture == textureIndex && !(partTexture == 0 && materialsHaveTextures)) { @@ -1736,34 +1736,34 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (!clusters.contains(clusterID)) { continue; } - FBXCluster fbxCluster; + HFMCluster hfmCluster; const Cluster& cluster = clusters[clusterID]; clusterIDs.append(clusterID); // see http://stackoverflow.com/questions/13566608/loading-skinning-information-from-fbx for a discussion // of skinning information in FBX QString jointID = _connectionChildMap.value(clusterID); - fbxCluster.jointIndex = modelIDs.indexOf(jointID); - if (fbxCluster.jointIndex == -1) { + hfmCluster.jointIndex = modelIDs.indexOf(jointID); + if (hfmCluster.jointIndex == -1) { qCDebug(modelformat) << "Joint not in model list: " << jointID; - fbxCluster.jointIndex = 0; + hfmCluster.jointIndex = 0; } - fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; + hfmCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform; // slam bottom row to (0, 0, 0, 1), we KNOW this is not a perspective matrix and // sometimes floating point fuzz can be introduced after the inverse. - fbxCluster.inverseBindMatrix[0][3] = 0.0f; - fbxCluster.inverseBindMatrix[1][3] = 0.0f; - fbxCluster.inverseBindMatrix[2][3] = 0.0f; - fbxCluster.inverseBindMatrix[3][3] = 1.0f; + hfmCluster.inverseBindMatrix[0][3] = 0.0f; + hfmCluster.inverseBindMatrix[1][3] = 0.0f; + hfmCluster.inverseBindMatrix[2][3] = 0.0f; + hfmCluster.inverseBindMatrix[3][3] = 1.0f; - fbxCluster.inverseBindTransform = Transform(fbxCluster.inverseBindMatrix); + hfmCluster.inverseBindTransform = Transform(hfmCluster.inverseBindMatrix); - extracted.mesh.clusters.append(fbxCluster); + extracted.mesh.clusters.append(hfmCluster); // override the bind rotation with the transform link - FBXJoint& joint = geometry.joints[fbxCluster.jointIndex]; + HFMJoint& joint = geometry.joints[hfmCluster.jointIndex]; joint.inverseBindRotation = glm::inverse(extractRotation(cluster.transformLink)); joint.bindTransform = cluster.transformLink; joint.bindTransformFoundInCluster = true; @@ -1776,7 +1776,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS // if we don't have a skinned joint, parent to the model itself if (extracted.mesh.clusters.isEmpty()) { - FBXCluster cluster; + HFMCluster cluster; cluster.jointIndex = modelIDs.indexOf(modelID); if (cluster.jointIndex == -1) { qCDebug(modelformat) << "Model not in model list: " << modelID; @@ -1786,7 +1786,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } // whether we're skinned depends on how many clusters are attached - const FBXCluster& firstFBXCluster = extracted.mesh.clusters.at(0); + const HFMCluster& firstHFMCluster = extracted.mesh.clusters.at(0); glm::mat4 inverseModelTransform = glm::inverse(modelTransform); if (clusterIDs.size() > 1) { // this is a multi-mesh joint @@ -1799,9 +1799,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS for (int i = 0; i < clusterIDs.size(); i++) { QString clusterID = clusterIDs.at(i); const Cluster& cluster = clusters[clusterID]; - const FBXCluster& fbxCluster = extracted.mesh.clusters.at(i); - int jointIndex = fbxCluster.jointIndex; - FBXJoint& joint = geometry.joints[jointIndex]; + const HFMCluster& hfmCluster = extracted.mesh.clusters.at(i); + int jointIndex = hfmCluster.jointIndex; + HFMJoint& joint = geometry.joints[jointIndex]; glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform; glm::vec3 boneEnd = extractTranslation(transformJointToMesh); glm::vec3 boneBegin = boneEnd; @@ -1881,8 +1881,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } else { // this is a single-mesh joint - int jointIndex = firstFBXCluster.jointIndex; - FBXJoint& joint = geometry.joints[jointIndex]; + int jointIndex = firstHFMCluster.jointIndex; + HFMJoint& joint = geometry.joints[jointIndex]; // transform cluster vertices to joint-frame and save for later glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; @@ -1924,7 +1924,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS // now that all joints have been scanned compute a k-Dop bounding volume of mesh for (int i = 0; i < geometry.joints.size(); ++i) { - FBXJoint& joint = geometry.joints[i]; + HFMJoint& joint = geometry.joints[i]; // NOTE: points are in joint-frame ShapeVertices& points = shapeVertices.at(i); @@ -1994,13 +1994,13 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS return geometryPtr; } -FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +HFMGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { QBuffer buffer(const_cast(&model)); buffer.open(QIODevice::ReadOnly); return readFBX(&buffer, mapping, url, loadLightmaps, lightmapLevel); } -FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { +HFMGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url, bool loadLightmaps, float lightmapLevel) { FBXReader reader; reader._rootNode = FBXReader::parseFBX(device); reader._loadLightmaps = loadLightmaps; @@ -2008,5 +2008,5 @@ FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QStri qCDebug(modelformat) << "Reading FBX: " << url; - return reader.extractFBXGeometry(mapping, url); + return reader.extractHFMGeometry(mapping, url); } diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index c391ea6647..f95ba7fe73 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -36,11 +36,11 @@ class FBXNode; /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); +HFMGeometry* readFBX(const QByteArray& model, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing -FBXGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); +HFMGeometry* readFBX(QIODevice* device, const QVariantHash& mapping, const QString& url = "", bool loadLightmaps = true, float lightmapLevel = 1.0f); class TextureParam { public: @@ -103,20 +103,20 @@ class ExtractedMesh; class FBXReader { public: - FBXGeometry* _fbxGeometry; + HFMGeometry* _hfmGeometry; FBXNode _rootNode; static FBXNode parseFBX(QIODevice* device); - FBXGeometry* extractFBXGeometry(const QVariantHash& mapping, const QString& url); + HFMGeometry* extractHFMGeometry(const QVariantHash& mapping, const QString& url); static ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate = true); QHash meshes; - static void buildModelMesh(FBXMesh& extractedMesh, const QString& url); + static void buildModelMesh(HFMMesh& extractedMesh, const QString& url); static glm::vec3 normalizeDirForPacking(const glm::vec3& dir); - FBXTexture getTexture(const QString& textureID); + HFMTexture getTexture(const QString& textureID); QHash _textureNames; // Hashes the original RelativeFilename of textures @@ -142,9 +142,9 @@ public: QHash ambientFactorTextures; QHash occlusionTextures; - QHash _fbxMaterials; + QHash _hfmMaterials; - void consolidateFBXMaterials(const QVariantHash& mapping); + void consolidateHFMMaterials(const QVariantHash& mapping); bool _loadLightmaps = true; float _lightmapOffset = 0.0f; diff --git a/libraries/fbx/src/FBXReader_Material.cpp b/libraries/fbx/src/FBXReader_Material.cpp index d5902962e5..ff1de30b97 100644 --- a/libraries/fbx/src/FBXReader_Material.cpp +++ b/libraries/fbx/src/FBXReader_Material.cpp @@ -27,7 +27,7 @@ #include "ModelFormatLogging.h" -void FBXMaterial::getTextureNames(QSet& textureList) const { +void HFMMaterial::getTextureNames(QSet& textureList) const { if (!normalTexture.isNull()) { textureList.insert(normalTexture.name); } @@ -63,7 +63,7 @@ void FBXMaterial::getTextureNames(QSet& textureList) const { } } -void FBXMaterial::setMaxNumPixelsPerTexture(int maxNumPixels) { +void HFMMaterial::setMaxNumPixelsPerTexture(int maxNumPixels) { normalTexture.maxNumPixels = maxNumPixels; albedoTexture.maxNumPixels = maxNumPixels; opacityTexture.maxNumPixels = maxNumPixels; @@ -77,12 +77,12 @@ void FBXMaterial::setMaxNumPixelsPerTexture(int maxNumPixels) { lightmapTexture.maxNumPixels = maxNumPixels; } -bool FBXMaterial::needTangentSpace() const { +bool HFMMaterial::needTangentSpace() const { return !normalTexture.isNull(); } -FBXTexture FBXReader::getTexture(const QString& textureID) { - FBXTexture texture; +HFMTexture FBXReader::getTexture(const QString& textureID) { + HFMTexture texture; const QByteArray& filepath = _textureFilepaths.value(textureID); texture.content = _textureContent.value(filepath); @@ -123,7 +123,7 @@ FBXTexture FBXReader::getTexture(const QString& textureID) { return texture; } -void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { +void FBXReader::consolidateHFMMaterials(const QVariantHash& mapping) { QString materialMapString = mapping.value("materialMap").toString(); QJsonDocument materialMapDocument = QJsonDocument::fromJson(materialMapString.toUtf8()); @@ -133,16 +133,16 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { qCDebug(modelformat) << "fbx Material Map found but did not produce valid JSON:" << materialMapString; } } - for (QHash::iterator it = _fbxMaterials.begin(); it != _fbxMaterials.end(); it++) { - FBXMaterial& material = (*it); + for (QHash::iterator it = _hfmMaterials.begin(); it != _hfmMaterials.end(); it++) { + HFMMaterial& material = (*it); // Maya is the exporting the shading model and we are trying to use it bool isMaterialLambert = (material.shadingModel.toLower() == "lambert"); // the pure material associated with this part bool detectDifferentUVs = false; - FBXTexture diffuseTexture; - FBXTexture diffuseFactorTexture; + HFMTexture diffuseTexture; + HFMTexture diffuseFactorTexture; QString diffuseTextureID = diffuseTextures.value(material.materialID); QString diffuseFactorTextureID = diffuseFactorTextures.value(material.materialID); @@ -169,7 +169,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs = (diffuseTexture.texcoordSet != 0) || (!diffuseTexture.transform.isIdentity()); } - FBXTexture transparentTexture; + HFMTexture transparentTexture; QString transparentTextureID = transparentTextures.value(material.materialID); // If PBS Material, systematically bind the albedo texture as transparency texture and check for the alpha channel if (material.isPBSMaterial) { @@ -181,7 +181,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs |= (transparentTexture.texcoordSet != 0) || (!transparentTexture.transform.isIdentity()); } - FBXTexture normalTexture; + HFMTexture normalTexture; QString bumpTextureID = bumpTextures.value(material.materialID); QString normalTextureID = normalTextures.value(material.materialID); if (!normalTextureID.isNull()) { @@ -198,7 +198,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs |= (normalTexture.texcoordSet != 0) || (!normalTexture.transform.isIdentity()); } - FBXTexture specularTexture; + HFMTexture specularTexture; QString specularTextureID = specularTextures.value(material.materialID); if (!specularTextureID.isNull()) { specularTexture = getTexture(specularTextureID); @@ -206,7 +206,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { material.specularTexture = specularTexture; } - FBXTexture metallicTexture; + HFMTexture metallicTexture; QString metallicTextureID = metallicTextures.value(material.materialID); if (!metallicTextureID.isNull()) { metallicTexture = getTexture(metallicTextureID); @@ -214,7 +214,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { material.metallicTexture = metallicTexture; } - FBXTexture roughnessTexture; + HFMTexture roughnessTexture; QString roughnessTextureID = roughnessTextures.value(material.materialID); if (!roughnessTextureID.isNull()) { roughnessTexture = getTexture(roughnessTextureID); @@ -222,7 +222,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs |= (roughnessTexture.texcoordSet != 0) || (!roughnessTexture.transform.isIdentity()); } - FBXTexture shininessTexture; + HFMTexture shininessTexture; QString shininessTextureID = shininessTextures.value(material.materialID); if (!shininessTextureID.isNull()) { shininessTexture = getTexture(shininessTextureID); @@ -230,7 +230,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { detectDifferentUVs |= (shininessTexture.texcoordSet != 0) || (!shininessTexture.transform.isIdentity()); } - FBXTexture emissiveTexture; + HFMTexture emissiveTexture; QString emissiveTextureID = emissiveTextures.value(material.materialID); if (!emissiveTextureID.isNull()) { emissiveTexture = getTexture(emissiveTextureID); @@ -245,7 +245,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { } } - FBXTexture occlusionTexture; + HFMTexture occlusionTexture; QString occlusionTextureID = occlusionTextures.value(material.materialID); if (occlusionTextureID.isNull()) { // 2nd chance @@ -265,7 +265,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { lightmapParams.x = _lightmapOffset; lightmapParams.y = _lightmapLevel; - FBXTexture ambientTexture; + HFMTexture ambientTexture; QString ambientTextureID = ambientTextures.value(material.materialID); if (ambientTextureID.isNull()) { // 2nd chance @@ -326,7 +326,7 @@ void FBXReader::consolidateFBXMaterials(const QVariantHash& mapping) { if (materialOptions.contains("scatteringMap")) { QByteArray scatteringMap = materialOptions.value("scatteringMap").toVariant().toByteArray(); - material.scatteringTexture = FBXTexture(); + material.scatteringTexture = HFMTexture(); material.scatteringTexture.name = material.name + ".scatteringMap"; material.scatteringTexture.filename = scatteringMap; } diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index c9b004c3a8..e098aff99a 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -42,9 +42,9 @@ using vec2h = glm::tvec2; -#define FBX_PACK_COLORS 1 +#define HFM_PACK_COLORS 1 -#if FBX_PACK_COLORS +#if HFM_PACK_COLORS using ColorType = glm::uint32; #define FBX_COLOR_ELEMENT gpu::Element::COLOR_RGBA_32 #else @@ -469,7 +469,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn QPair materialTexture(materialID, 0); - // grab or setup the FBXMeshPart for the part this face belongs to + // grab or setup the HFMMeshPart for the part this face belongs to int& partIndexPlusOne = materialTextureParts[materialTexture]; if (partIndexPlusOne == 0) { data.extracted.partMaterialTextures.append(materialTexture); @@ -478,7 +478,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn } // give the mesh part this index - FBXMeshPart& part = data.extracted.mesh.parts[partIndexPlusOne - 1]; + HFMMeshPart& part = data.extracted.mesh.parts[partIndexPlusOne - 1]; part.triangleIndices.append(firstCorner.value()); part.triangleIndices.append(dracoFace[1].value()); part.triangleIndices.append(dracoFace[2].value()); @@ -511,7 +511,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn data.extracted.mesh.parts.resize(data.extracted.mesh.parts.size() + 1); partIndex = data.extracted.mesh.parts.size(); } - FBXMeshPart& part = data.extracted.mesh.parts[partIndex - 1]; + HFMMeshPart& part = data.extracted.mesh.parts[partIndex - 1]; if (endIndex - beginIndex == 4) { appendIndex(data, part.quadIndices, beginIndex++, deduplicate); @@ -565,9 +565,9 @@ glm::vec3 FBXReader::normalizeDirForPacking(const glm::vec3& dir) { return dir; } -void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { +void FBXReader::buildModelMesh(HFMMesh& extractedMesh, const QString& url) { unsigned int totalSourceIndices = 0; - foreach(const FBXMeshPart& part, extractedMesh.parts) { + foreach(const HFMMeshPart& part, extractedMesh.parts) { totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); } @@ -583,17 +583,17 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { return; } - FBXMesh& fbxMesh = extractedMesh; + HFMMesh& hfmMesh = extractedMesh; graphics::MeshPointer mesh(new graphics::Mesh()); int numVerts = extractedMesh.vertices.size(); - if (!fbxMesh.normals.empty() && fbxMesh.tangents.empty()) { + if (!hfmMesh.normals.empty() && hfmMesh.tangents.empty()) { // Fill with a dummy value to force tangents to be present if there are normals - fbxMesh.tangents.reserve(fbxMesh.normals.size()); - std::fill_n(std::back_inserter(fbxMesh.tangents), fbxMesh.normals.size(), Vectors::UNIT_X); + hfmMesh.tangents.reserve(hfmMesh.normals.size()); + std::fill_n(std::back_inserter(hfmMesh.tangents), hfmMesh.normals.size(), Vectors::UNIT_X); } // Same thing with blend shapes - for (auto& blendShape : fbxMesh.blendshapes) { + for (auto& blendShape : hfmMesh.blendshapes) { if (!blendShape.normals.empty() && blendShape.tangents.empty()) { // Fill with a dummy value to force tangents to be present if there are normals blendShape.tangents.reserve(blendShape.normals.size()); @@ -609,8 +609,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // 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(); + const int normalsSize = hfmMesh.normals.size() * normalElement.getSize(); + const int tangentsSize = hfmMesh.tangents.size() * normalElement.getSize(); // If there are normals then there should be tangents assert(normalsSize <= tangentsSize); if (tangentsSize > normalsSize) { @@ -620,22 +620,22 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Color attrib const auto colorElement = FBX_COLOR_ELEMENT; - const int colorsSize = fbxMesh.colors.size() * colorElement.getSize(); + const int colorsSize = hfmMesh.colors.size() * colorElement.getSize(); // Texture coordinates are stored in 2 half floats 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(); + const int texCoordsSize = hfmMesh.texCoords.size() * texCoordsElement.getSize(); + const int texCoords1Size = hfmMesh.texCoords1.size() * texCoordsElement.getSize(); // 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)); + const auto clusterIndiceElement = (hfmMesh.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); // 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 numVertClusters = (hfmMesh.clusterIndices.size() == hfmMesh.clusterWeights.size() ? hfmMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); const int clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); const int clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); @@ -660,9 +660,9 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { if (normalsSize > 0) { std::vector normalsAndTangents; - normalsAndTangents.reserve(fbxMesh.normals.size() + fbxMesh.tangents.size()); - for (auto normalIt = fbxMesh.normals.constBegin(), tangentIt = fbxMesh.tangents.constBegin(); - normalIt != fbxMesh.normals.constEnd(); + normalsAndTangents.reserve(hfmMesh.normals.size() + hfmMesh.tangents.size()); + for (auto normalIt = hfmMesh.normals.constBegin(), tangentIt = hfmMesh.tangents.constBegin(); + normalIt != hfmMesh.normals.constEnd(); ++normalIt, ++tangentIt) { #if FBX_PACK_NORMALS const auto normal = normalizeDirForPacking(*normalIt); @@ -681,24 +681,24 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Pack colors if (colorsSize > 0) { -#if FBX_PACK_COLORS +#if HFM_PACK_COLORS std::vector colors; - colors.reserve(fbxMesh.colors.size()); - for (const auto& color : fbxMesh.colors) { + colors.reserve(hfmMesh.colors.size()); + for (const auto& color : hfmMesh.colors) { colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); } vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); #else - vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) hfmMesh.colors.constData()); #endif } // Pack Texcoords 0 and 1 (if exists) if (texCoordsSize > 0) { QVector texCoordData; - texCoordData.reserve(fbxMesh.texCoords.size()); - for (auto& texCoordVec2f : fbxMesh.texCoords) { + texCoordData.reserve(hfmMesh.texCoords.size()); + for (auto& texCoordVec2f : hfmMesh.texCoords) { vec2h texCoordVec2h; texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); @@ -709,8 +709,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { } if (texCoords1Size > 0) { QVector texCoordData; - texCoordData.reserve(fbxMesh.texCoords1.size()); - for (auto& texCoordVec2f : fbxMesh.texCoords1) { + texCoordData.reserve(hfmMesh.texCoords1.size()); + for (auto& texCoordVec2f : hfmMesh.texCoords1) { vec2h texCoordVec2h; texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); @@ -722,22 +722,22 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Clusters data if (clusterIndicesSize > 0) { - if (fbxMesh.clusters.size() < UINT8_MAX) { + if (hfmMesh.clusters.size() < UINT8_MAX) { // yay! we can fit the clusterIndices within 8-bits - int32_t numIndices = fbxMesh.clusterIndices.size(); + int32_t numIndices = hfmMesh.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]); + assert(hfmMesh.clusterIndices[i] <= UINT8_MAX); + clusterIndices[i] = (uint8_t)(hfmMesh.clusterIndices[i]); } vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); } else { - vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) hfmMesh.clusterIndices.constData()); } } if (clusterWeightsSize > 0) { - vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); + vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) hfmMesh.clusterWeights.constData()); } @@ -856,7 +856,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { // Index and Part Buffers unsigned int totalIndices = 0; - foreach(const FBXMeshPart& part, extractedMesh.parts) { + foreach(const HFMMeshPart& part, extractedMesh.parts) { totalIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); } @@ -875,7 +875,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { if (extractedMesh.parts.size() > 1) { indexNum = 0; } - foreach(const FBXMeshPart& part, extractedMesh.parts) { + foreach(const HFMMeshPart& part, extractedMesh.parts) { graphics::Mesh::Part modelPart(indexNum, 0, 0, graphics::Mesh::TRIANGLES); if (part.quadTrianglesIndices.size()) { diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp index b93dc3541b..05534b5264 100644 --- a/libraries/fbx/src/GLTFReader.cpp +++ b/libraries/fbx/src/GLTFReader.cpp @@ -697,7 +697,7 @@ glm::mat4 GLTFReader::getModelTransform(const GLTFNode& node) { return tmat; } -bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { +bool GLTFReader::buildGeometry(HFMGeometry& geometry, const QUrl& url) { //Build dependencies QVector> nodeDependencies(_file.nodes.size()); @@ -750,10 +750,10 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { for (int i = 0; i < materialIDs.size(); i++) { QString& matid = materialIDs[i]; - geometry.materials[matid] = FBXMaterial(); - FBXMaterial& fbxMaterial = geometry.materials[matid]; - fbxMaterial._material = std::make_shared(); - setFBXMaterial(fbxMaterial, _file.materials[i]); + geometry.materials[matid] = HFMMaterial(); + HFMMaterial& hfmMaterial = geometry.materials[matid]; + hfmMaterial._material = std::make_shared(); + setHFMMaterial(hfmMaterial, _file.materials[i]); } @@ -765,9 +765,9 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { if (node.defined["mesh"]) { qCDebug(modelformat) << "node_transforms" << node.transforms; foreach(auto &primitive, _file.meshes[node.mesh].primitives) { - geometry.meshes.append(FBXMesh()); - FBXMesh& mesh = geometry.meshes[geometry.meshes.size() - 1]; - FBXCluster cluster; + geometry.meshes.append(HFMMesh()); + HFMMesh& mesh = geometry.meshes[geometry.meshes.size() - 1]; + HFMCluster cluster; cluster.jointIndex = 0; cluster.inverseBindMatrix = glm::mat4(1, 0, 0, 0, 0, 1, 0, 0, @@ -775,7 +775,7 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { 0, 0, 0, 1); mesh.clusters.append(cluster); - FBXMeshPart part = FBXMeshPart(); + HFMMeshPart part = HFMMeshPart(); int indicesAccessorIdx = primitive.indices; @@ -910,7 +910,7 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { return true; } -FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, +HFMGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) { _url = url; @@ -924,8 +924,8 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping parseGLTF(model); //_file.dump(); - FBXGeometry* geometryPtr = new FBXGeometry(); - FBXGeometry& geometry = *geometryPtr; + HFMGeometry* geometryPtr = new HFMGeometry(); + HFMGeometry& geometry = *geometryPtr; buildGeometry(geometry, url); @@ -997,8 +997,8 @@ QNetworkReply* GLTFReader::request(QUrl& url, bool isTest) { return netReply; // trying to sync later on. } -FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { - FBXTexture fbxtex = FBXTexture(); +HFMTexture GLTFReader::getHFMTexture(const GLTFTexture& texture) { + HFMTexture fbxtex = HFMTexture(); fbxtex.texcoordSet = 0; if (texture.defined["source"]) { @@ -1014,7 +1014,7 @@ FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { return fbxtex; } -void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& material) { +void GLTFReader::setHFMMaterial(HFMMaterial& fbxmat, const GLTFMaterial& material) { if (material.defined["name"]) { @@ -1029,17 +1029,17 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia } if (material.defined["emissiveTexture"]) { - fbxmat.emissiveTexture = getFBXTexture(_file.textures[material.emissiveTexture]); + fbxmat.emissiveTexture = getHFMTexture(_file.textures[material.emissiveTexture]); fbxmat.useEmissiveMap = true; } if (material.defined["normalTexture"]) { - fbxmat.normalTexture = getFBXTexture(_file.textures[material.normalTexture]); + fbxmat.normalTexture = getHFMTexture(_file.textures[material.normalTexture]); fbxmat.useNormalMap = true; } if (material.defined["occlusionTexture"]) { - fbxmat.occlusionTexture = getFBXTexture(_file.textures[material.occlusionTexture]); + fbxmat.occlusionTexture = getHFMTexture(_file.textures[material.occlusionTexture]); fbxmat.useOcclusionMap = true; } @@ -1050,14 +1050,14 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia fbxmat.metallic = material.pbrMetallicRoughness.metallicFactor; } if (material.pbrMetallicRoughness.defined["baseColorTexture"]) { - fbxmat.opacityTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); - fbxmat.albedoTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); + fbxmat.opacityTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); + fbxmat.albedoTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); fbxmat.useAlbedoMap = true; } if (material.pbrMetallicRoughness.defined["metallicRoughnessTexture"]) { - fbxmat.roughnessTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.roughnessTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); fbxmat.useRoughnessMap = true; - fbxmat.metallicTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.metallicTexture = getHFMTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); fbxmat.useMetallicMap = true; } if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { @@ -1181,8 +1181,8 @@ void GLTFReader::retriangulate(const QVector& inIndices, const QVector materialMeshIdMap; - QVector fbxMeshParts; + QVector hfmMeshParts; for (int i = 0, meshPartCount = 0; i < mesh.parts.count(); i++, meshPartCount++) { - FBXMeshPart& meshPart = mesh.parts[i]; + HFMMeshPart& meshPart = mesh.parts[i]; FaceGroup faceGroup = faceGroups[meshPartCount]; bool specifiesUV = false; foreach(OBJFace face, faceGroup) { // Go through all of the OBJ faces and determine the number of different materials necessary (each different material will be a unique mesh). // NOTE (trent/mittens 3/30/17): this seems hardcore wasteful and is slowed down a bit by iterating through the face group twice, but it's the best way I've thought of to hack multi-material support in an OBJ into this pipeline. if (!materialMeshIdMap.contains(face.materialName)) { - // Create a new FBXMesh for this material mapping. + // Create a new HFMMesh for this material mapping. materialMeshIdMap.insert(face.materialName, materialMeshIdMap.count()); - fbxMeshParts.append(FBXMeshPart()); - FBXMeshPart& meshPartNew = fbxMeshParts.last(); + hfmMeshParts.append(HFMMeshPart()); + HFMMeshPart& meshPartNew = hfmMeshParts.last(); meshPartNew.quadIndices = QVector(meshPart.quadIndices); // Copy over quad indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. meshPartNew.quadTrianglesIndices = QVector(meshPart.quadTrianglesIndices); // Copy over quad triangulated indices [NOTE (trent/mittens, 4/3/17): Likely unnecessary since they go unused anyway]. meshPartNew.triangleIndices = QVector(meshPart.triangleIndices); // Copy over triangle indices. @@ -745,14 +745,14 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m // clean up old mesh parts. int unmodifiedMeshPartCount = mesh.parts.count(); mesh.parts.clear(); - mesh.parts = QVector(fbxMeshParts); + mesh.parts = QVector(hfmMeshParts); for (int i = 0, meshPartCount = 0; i < unmodifiedMeshPartCount; i++, meshPartCount++) { FaceGroup faceGroup = faceGroups[meshPartCount]; // Now that each mesh has been created with its own unique material mappings, fill them with data (vertex data is duplicated, face data is not). foreach(OBJFace face, faceGroup) { - FBXMeshPart& meshPart = mesh.parts[materialMeshIdMap[face.materialName]]; + HFMMeshPart& meshPart = mesh.parts[materialMeshIdMap[face.materialName]]; glm::vec3 v0 = checked_at(vertices, face.vertexIndices[0]); glm::vec3 v1 = checked_at(vertices, face.vertexIndices[1]); @@ -885,38 +885,38 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m if (!objMaterial.used) { continue; } - geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor, + geometry.materials[materialID] = HFMMaterial(objMaterial.diffuseColor, objMaterial.specularColor, objMaterial.emissiveColor, objMaterial.shininess, objMaterial.opacity); - FBXMaterial& fbxMaterial = geometry.materials[materialID]; - fbxMaterial.materialID = materialID; - fbxMaterial._material = std::make_shared(); - graphics::MaterialPointer modelMaterial = fbxMaterial._material; + HFMMaterial& hfmMaterial = geometry.materials[materialID]; + hfmMaterial.materialID = materialID; + hfmMaterial._material = std::make_shared(); + graphics::MaterialPointer modelMaterial = hfmMaterial._material; if (!objMaterial.diffuseTextureFilename.isEmpty()) { - fbxMaterial.albedoTexture.filename = objMaterial.diffuseTextureFilename; + hfmMaterial.albedoTexture.filename = objMaterial.diffuseTextureFilename; } if (!objMaterial.specularTextureFilename.isEmpty()) { - fbxMaterial.specularTexture.filename = objMaterial.specularTextureFilename; + hfmMaterial.specularTexture.filename = objMaterial.specularTextureFilename; } if (!objMaterial.emissiveTextureFilename.isEmpty()) { - fbxMaterial.emissiveTexture.filename = objMaterial.emissiveTextureFilename; + hfmMaterial.emissiveTexture.filename = objMaterial.emissiveTextureFilename; } if (!objMaterial.bumpTextureFilename.isEmpty()) { - fbxMaterial.normalTexture.filename = objMaterial.bumpTextureFilename; - fbxMaterial.normalTexture.isBumpmap = true; - fbxMaterial.bumpMultiplier = objMaterial.bumpTextureOptions.bumpMultiplier; + hfmMaterial.normalTexture.filename = objMaterial.bumpTextureFilename; + hfmMaterial.normalTexture.isBumpmap = true; + hfmMaterial.bumpMultiplier = objMaterial.bumpTextureOptions.bumpMultiplier; } if (!objMaterial.opacityTextureFilename.isEmpty()) { - fbxMaterial.opacityTexture.filename = objMaterial.opacityTextureFilename; + hfmMaterial.opacityTexture.filename = objMaterial.opacityTextureFilename; } - modelMaterial->setEmissive(fbxMaterial.emissiveColor); - modelMaterial->setAlbedo(fbxMaterial.diffuseColor); - modelMaterial->setMetallic(glm::length(fbxMaterial.specularColor)); - modelMaterial->setRoughness(graphics::Material::shininessToRoughness(fbxMaterial.shininess)); + modelMaterial->setEmissive(hfmMaterial.emissiveColor); + modelMaterial->setAlbedo(hfmMaterial.diffuseColor); + modelMaterial->setMetallic(glm::length(hfmMaterial.specularColor)); + modelMaterial->setRoughness(graphics::Material::shininessToRoughness(hfmMaterial.shininess)); bool applyTransparency = false; bool applyShininess = false; @@ -971,7 +971,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m } if (applyTransparency) { - fbxMaterial.opacity = std::max(fbxMaterial.opacity, ILLUMINATION_MODEL_MIN_OPACITY); + hfmMaterial.opacity = std::max(hfmMaterial.opacity, ILLUMINATION_MODEL_MIN_OPACITY); } if (applyShininess) { modelMaterial->setRoughness(ILLUMINATION_MODEL_APPLY_SHININESS); @@ -985,18 +985,18 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m modelMaterial->setFresnel(glm::vec3(1.0f)); } - modelMaterial->setOpacity(fbxMaterial.opacity); + modelMaterial->setOpacity(hfmMaterial.opacity); } return geometryPtr; } -void fbxDebugDump(const FBXGeometry& fbxgeo) { - qCDebug(modelformat) << "---------------- fbxGeometry ----------------"; +void fbxDebugDump(const HFMGeometry& fbxgeo) { + qCDebug(modelformat) << "---------------- hfmGeometry ----------------"; qCDebug(modelformat) << " hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints; qCDebug(modelformat) << " offset =" << fbxgeo.offset; qCDebug(modelformat) << " meshes.count() =" << fbxgeo.meshes.count(); - foreach (FBXMesh mesh, fbxgeo.meshes) { + foreach (HFMMesh mesh, fbxgeo.meshes) { qCDebug(modelformat) << " vertices.count() =" << mesh.vertices.count(); qCDebug(modelformat) << " colors.count() =" << mesh.colors.count(); qCDebug(modelformat) << " normals.count() =" << mesh.normals.count(); @@ -1014,7 +1014,7 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << " meshExtents =" << mesh.meshExtents; qCDebug(modelformat) << " modelTransform =" << mesh.modelTransform; qCDebug(modelformat) << " parts.count() =" << mesh.parts.count(); - foreach (FBXMeshPart meshPart, mesh.parts) { + foreach (HFMMeshPart meshPart, mesh.parts) { qCDebug(modelformat) << " quadIndices.count() =" << meshPart.quadIndices.count(); qCDebug(modelformat) << " triangleIndices.count() =" << meshPart.triangleIndices.count(); /* @@ -1031,7 +1031,7 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { */ } qCDebug(modelformat) << " clusters.count() =" << mesh.clusters.count(); - foreach (FBXCluster cluster, mesh.clusters) { + foreach (HFMCluster cluster, mesh.clusters) { qCDebug(modelformat) << " jointIndex =" << cluster.jointIndex; qCDebug(modelformat) << " inverseBindMatrix =" << cluster.inverseBindMatrix; } @@ -1040,7 +1040,7 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << " jointIndices =" << fbxgeo.jointIndices; qCDebug(modelformat) << " joints.count() =" << fbxgeo.joints.count(); - foreach (FBXJoint joint, fbxgeo.joints) { + foreach (HFMJoint joint, fbxgeo.joints) { qCDebug(modelformat) << " isFree =" << joint.isFree; qCDebug(modelformat) << " freeLineage" << joint.freeLineage; qCDebug(modelformat) << " parentIndex" << joint.parentIndex; diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index e432a3ea51..edfe6f23e8 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -42,7 +42,7 @@ public: bool add(const QByteArray& vertexIndex, const QByteArray& textureIndex, const QByteArray& normalIndex, const QVector& vertices, const QVector& vertexColors); // Return a set of one or more OBJFaces from this one, in which each is just a triangle. - // Even though FBXMeshPart can handle quads, it would be messy to try to keep track of mixed-size faces, so we treat everything as triangles. + // Even though HFMMeshPart can handle quads, it would be messy to try to keep track of mixed-size faces, so we treat everything as triangles. QVector triangulate(); private: void addFrom(const OBJFace* face, int index); @@ -54,7 +54,7 @@ public: } ; // Materials and references to material names can come in any order, and different mesh parts can refer to the same material. -// Therefore it would get pretty hacky to try to use FBXMeshPart to store these as we traverse the files. +// Therefore it would get pretty hacky to try to use HFMMeshPart to store these as we traverse the files. class OBJMaterial { public: float shininess; @@ -87,13 +87,13 @@ public: QString currentMaterialName; QHash materials; - FBXGeometry::Pointer readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); + HFMGeometry::Pointer readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl()); private: QUrl _url; QHash librariesSeen; - bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, + bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, HFMGeometry& geometry, float& scaleGuess, bool combineParts); void parseMaterialLibrary(QIODevice* device); void parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions); @@ -103,5 +103,5 @@ private: }; // What are these utilities doing here? One is used by fbx loading code in VHACD Utils, and the other a general debugging utility. -void setMeshPartDefaults(FBXMeshPart& meshPart, QString materialID); -void fbxDebugDump(const FBXGeometry& fbxgeo); +void setMeshPartDefaults(HFMMeshPart& meshPart, QString materialID); +void fbxDebugDump(const HFMGeometry& fbxgeo); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index e96815d391..a950e1df3c 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -128,7 +128,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { void GeometryMappingResource::onGeometryMappingLoaded(bool success) { if (success && _geometryResource) { - _fbxGeometry = _geometryResource->_fbxGeometry; + _hfmGeometry = _geometryResource->_hfmGeometry; _meshParts = _geometryResource->_meshParts; _meshes = _geometryResource->_meshes; _materials = _geometryResource->_materials; @@ -193,38 +193,38 @@ void GeometryReader::run() { _url.path().toLower().endsWith(".obj.gz") || _url.path().toLower().endsWith(".gltf"))) { - FBXGeometry::Pointer fbxGeometry; + HFMGeometry::Pointer hfmGeometry; if (_url.path().toLower().endsWith(".fbx")) { - fbxGeometry.reset(readFBX(_data, _mapping, _url.path())); - if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) { + hfmGeometry.reset(readFBX(_data, _mapping, _url.path())); + if (hfmGeometry->meshes.size() == 0 && hfmGeometry->joints.size() == 0) { throw QString("empty geometry, possibly due to an unsupported FBX version"); } } else if (_url.path().toLower().endsWith(".obj")) { - fbxGeometry = OBJReader().readOBJ(_data, _mapping, _combineParts, _url); + hfmGeometry = OBJReader().readOBJ(_data, _mapping, _combineParts, _url); } else if (_url.path().toLower().endsWith(".obj.gz")) { QByteArray uncompressedData; if (gunzip(_data, uncompressedData)){ - fbxGeometry = OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url); + hfmGeometry = OBJReader().readOBJ(uncompressedData, _mapping, _combineParts, _url); } else { throw QString("failed to decompress .obj.gz"); } } else if (_url.path().toLower().endsWith(".gltf")) { std::shared_ptr glreader = std::make_shared(); - fbxGeometry.reset(glreader->readGLTF(_data, _mapping, _url)); - if (fbxGeometry->meshes.size() == 0 && fbxGeometry->joints.size() == 0) { + hfmGeometry.reset(glreader->readGLTF(_data, _mapping, _url)); + if (hfmGeometry->meshes.size() == 0 && hfmGeometry->joints.size() == 0) { throw QString("empty geometry, possibly due to an unsupported GLTF version"); } } else { throw QString("unsupported format"); } - // Add scripts to fbxgeometry + // Add scripts to hfmGeometry if (!_mapping.value(SCRIPT_FIELD).isNull()) { QVariantList scripts = _mapping.values(SCRIPT_FIELD); for (auto &script : scripts) { - fbxGeometry->scripts.push_back(script.toString()); + hfmGeometry->scripts.push_back(script.toString()); } } @@ -234,7 +234,7 @@ void GeometryReader::run() { qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; } else { QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition", - Q_ARG(FBXGeometry::Pointer, fbxGeometry)); + Q_ARG(HFMGeometry::Pointer, hfmGeometry)); } } else { throw QString("url is invalid"); @@ -262,7 +262,7 @@ public: virtual void downloadFinished(const QByteArray& data) override; protected: - Q_INVOKABLE void setGeometryDefinition(FBXGeometry::Pointer fbxGeometry); + Q_INVOKABLE void setGeometryDefinition(HFMGeometry::Pointer hfmGeometry); private: QVariantHash _mapping; @@ -277,13 +277,13 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { QThreadPool::globalInstance()->start(new GeometryReader(_self, _effectiveBaseURL, _mapping, data, _combineParts)); } -void GeometryDefinitionResource::setGeometryDefinition(FBXGeometry::Pointer fbxGeometry) { +void GeometryDefinitionResource::setGeometryDefinition(HFMGeometry::Pointer hfmGeometry) { // Assume ownership of the geometry pointer - _fbxGeometry = fbxGeometry; + _hfmGeometry = hfmGeometry; // Copy materials QHash materialIDAtlas; - for (const FBXMaterial& material : _fbxGeometry->materials) { + for (const HFMMaterial& material : _hfmGeometry->materials) { materialIDAtlas[material.materialID] = _materials.size(); _materials.push_back(std::make_shared(material, _textureBaseUrl)); } @@ -291,11 +291,11 @@ void GeometryDefinitionResource::setGeometryDefinition(FBXGeometry::Pointer fbxG std::shared_ptr meshes = std::make_shared(); std::shared_ptr parts = std::make_shared(); int meshID = 0; - for (const FBXMesh& mesh : _fbxGeometry->meshes) { + for (const HFMMesh& mesh : _hfmGeometry->meshes) { // Copy mesh pointers meshes->emplace_back(mesh._mesh); int partID = 0; - for (const FBXMeshPart& part : mesh.parts) { + for (const HFMMeshPart& part : mesh.parts) { // Construct local parts parts->push_back(std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID])); partID++; @@ -371,7 +371,7 @@ const QVariantMap Geometry::getTextures() const { // FIXME: The materials should only be copied when modified, but the Model currently caches the original Geometry::Geometry(const Geometry& geometry) { - _fbxGeometry = geometry._fbxGeometry; + _hfmGeometry = geometry._hfmGeometry; _meshes = geometry._meshes; _meshParts = geometry._meshParts; @@ -444,8 +444,8 @@ void GeometryResource::deleter() { } void GeometryResource::setTextures() { - if (_fbxGeometry) { - for (const FBXMaterial& material : _fbxGeometry->materials) { + if (_hfmGeometry) { + for (const HFMMaterial& material : _hfmGeometry->materials) { _materials.push_back(std::make_shared(material, _textureBaseUrl)); } } @@ -512,7 +512,7 @@ const QString& NetworkMaterial::getTextureName(MapChannel channel) { return NO_TEXTURE; } -QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const FBXTexture& texture) { +QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const HFMTexture& texture) { if (texture.content.isEmpty()) { // External file: search relative to the baseUrl, in case filename is relative return baseUrl.resolved(QUrl(texture.filename)); @@ -529,22 +529,22 @@ QUrl NetworkMaterial::getTextureUrl(const QUrl& baseUrl, const FBXTexture& textu } } -graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture, +graphics::TextureMapPointer NetworkMaterial::fetchTextureMap(const QUrl& baseUrl, const HFMTexture& hfmTexture, image::TextureUsage::Type type, MapChannel channel) { if (baseUrl.isEmpty()) { return nullptr; } - const auto url = getTextureUrl(baseUrl, fbxTexture); - const auto texture = DependencyManager::get()->getTexture(url, type, fbxTexture.content, fbxTexture.maxNumPixels); - _textures[channel] = Texture { fbxTexture.name, texture }; + const auto url = getTextureUrl(baseUrl, hfmTexture); + const auto texture = DependencyManager::get()->getTexture(url, type, hfmTexture.content, hfmTexture.maxNumPixels); + _textures[channel] = Texture { hfmTexture.name, texture }; auto map = std::make_shared(); if (texture) { map->setTextureSource(texture->_textureSource); } - map->setTextureTransform(fbxTexture.transform); + map->setTextureTransform(hfmTexture.transform); return map; } @@ -624,7 +624,7 @@ void NetworkMaterial::setLightmapMap(const QUrl& url) { } } -NetworkMaterial::NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl) : +NetworkMaterial::NetworkMaterial(const HFMMaterial& material, const QUrl& textureBaseUrl) : graphics::Material(*material._material), _textures(MapChannel::NUM_MAP_CHANNELS) { diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 5cbe96ea03..2283c355d8 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -45,9 +45,9 @@ public: // Mutable, but must retain structure of vector using NetworkMaterials = std::vector>; - bool isGeometryLoaded() const { return (bool)_fbxGeometry; } + bool isGeometryLoaded() const { return (bool)_hfmGeometry; } - const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; } + const HFMGeometry& getHFMGeometry() const { return *_hfmGeometry; } const GeometryMeshes& getMeshes() const { return *_meshes; } const std::shared_ptr getShapeMaterial(int shapeID) const; @@ -62,7 +62,7 @@ protected: friend class GeometryMappingResource; // Shared across all geometries, constant throughout lifetime - std::shared_ptr _fbxGeometry; + std::shared_ptr _hfmGeometry; std::shared_ptr _meshes; std::shared_ptr _meshParts; @@ -94,7 +94,7 @@ protected: // Geometries may not hold onto textures while cached - that is for the texture cache // Instead, these methods clear and reset textures from the geometry when caching/loading - bool shouldSetTextures() const { return _fbxGeometry && _materials.empty(); } + bool shouldSetTextures() const { return _hfmGeometry && _materials.empty(); } void setTextures(); void resetTextures(); @@ -165,7 +165,7 @@ public: using MapChannel = graphics::Material::MapChannel; NetworkMaterial() : _textures(MapChannel::NUM_MAP_CHANNELS) {} - NetworkMaterial(const FBXMaterial& material, const QUrl& textureBaseUrl); + NetworkMaterial(const HFMMaterial& material, const QUrl& textureBaseUrl); NetworkMaterial(const NetworkMaterial& material); void setAlbedoMap(const QUrl& url, bool useAlphaChannel); @@ -201,8 +201,8 @@ protected: private: // Helpers for the ctors - QUrl getTextureUrl(const QUrl& baseUrl, const FBXTexture& fbxTexture); - graphics::TextureMapPointer fetchTextureMap(const QUrl& baseUrl, const FBXTexture& fbxTexture, + QUrl getTextureUrl(const QUrl& baseUrl, const HFMTexture& hfmTexture); + graphics::TextureMapPointer fetchTextureMap(const QUrl& baseUrl, const HFMTexture& hfmTexture, image::TextureUsage::Type type, MapChannel channel); graphics::TextureMapPointer fetchTextureMap(const QUrl& url, image::TextureUsage::Type type, MapChannel channel); diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 81a017a46d..31d6cef060 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -32,8 +32,8 @@ bool CauterizedModel::updateGeometry() { bool needsFullUpdate = Model::updateGeometry(); if (_isCauterized && needsFullUpdate) { assert(_cauterizeMeshStates.empty()); - const FBXGeometry& fbxGeometry = getFBXGeometry(); - foreach (const FBXMesh& mesh, fbxGeometry.meshes) { + const HFMGeometry& hfmGeometry = getHFMGeometry(); + foreach (const HFMMesh& mesh, hfmGeometry.meshes) { Model::MeshState state; if (_useDualQuaternionSkinning) { state.clusterDualQuaternions.resize(mesh.clusters.size()); @@ -76,7 +76,7 @@ void CauterizedModel::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(); - const FBXGeometry& fbxGeometry = getFBXGeometry(); + const HFMGeometry& hfmGeometry = getHFMGeometry(); for (uint32_t i = 0; i < numMeshes; i++) { const auto& mesh = meshes.at(i); if (!mesh) { @@ -86,7 +86,7 @@ void CauterizedModel::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - initializeBlendshapes(fbxGeometry.meshes[i], i); + initializeBlendshapes(hfmGeometry.meshes[i], i); auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); _modelMeshRenderItems << std::static_pointer_cast(ptr); @@ -109,13 +109,13 @@ void CauterizedModel::updateClusterMatrices() { return; } _needsUpdateClusterMatrices = false; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); for (int i = 0; i < (int)_meshStates.size(); i++) { Model::MeshState& state = _meshStates[i]; - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); + const HFMCluster& cluster = mesh.clusters.at(j); if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); @@ -145,10 +145,10 @@ void CauterizedModel::updateClusterMatrices() { for (int i = 0; i < _cauterizeMeshStates.size(); i++) { Model::MeshState& state = _cauterizeMeshStates[i]; - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); + const HFMCluster& cluster = mesh.clusters.at(j); if (_useDualQuaternionSkinning) { if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 4ebd92bb05..8e2541fdda 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -260,8 +260,8 @@ void ModelMeshPartPayload::initCache(const ModelPointer& model) { _hasColorAttrib = vertexFormat->hasAttribute(gpu::Stream::COLOR); _isSkinned = vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT) && vertexFormat->hasAttribute(gpu::Stream::SKIN_CLUSTER_INDEX); - const FBXGeometry& geometry = model->getFBXGeometry(); - const FBXMesh& mesh = geometry.meshes.at(_meshIndex); + const HFMGeometry& geometry = model->getHFMGeometry(); + const HFMMesh& mesh = geometry.meshes.at(_meshIndex); _isBlendShaped = !mesh.blendshapes.isEmpty(); _hasTangents = !mesh.tangents.isEmpty(); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 53009e8bfa..65b3fef7c0 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -183,7 +183,7 @@ bool Model::shouldInvalidatePayloadShapeKey(int meshIndex) { return true; } - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); const auto& networkMeshes = getGeometry()->getMeshes(); // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown // to false to rebuild out mesh groups. @@ -278,7 +278,7 @@ void Model::setRenderItemsNeedUpdate() { void Model::reset() { if (isLoaded()) { - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); _rig.reset(geometry); emit rigReset(); emit rigReady(); @@ -295,13 +295,13 @@ bool Model::updateGeometry() { _needsReload = false; // TODO: should all Models have a valid _rig? - if (_rig.jointStatesEmpty() && getFBXGeometry().joints.size() > 0) { + if (_rig.jointStatesEmpty() && getHFMGeometry().joints.size() > 0) { initJointStates(); assert(_meshStates.empty()); - const FBXGeometry& fbxGeometry = getFBXGeometry(); + const HFMGeometry& hfmGeometry = getHFMGeometry(); int i = 0; - foreach (const FBXMesh& mesh, fbxGeometry.meshes) { + foreach (const HFMMesh& mesh, hfmGeometry.meshes) { MeshState state; state.clusterDualQuaternions.resize(mesh.clusters.size()); state.clusterMatrices.resize(mesh.clusters.size()); @@ -319,7 +319,7 @@ bool Model::updateGeometry() { // virtual void Model::initJointStates() { - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); _rig.initJointStates(geometry, modelOffset); @@ -363,7 +363,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g int bestShapeID = 0; int bestSubMeshIndex = 0; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); if (!_triangleSetsValid) { calculateTriangleSets(geometry); } @@ -506,7 +506,7 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co int bestShapeID = 0; int bestSubMeshIndex = 0; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); if (!_triangleSetsValid) { calculateTriangleSets(geometry); } @@ -641,7 +641,7 @@ bool Model::convexHullContains(glm::vec3 point) { QMutexLocker locker(&_mutex); if (!_triangleSetsValid) { - calculateTriangleSets(getFBXGeometry()); + calculateTriangleSets(getHFMGeometry()); } // If we are inside the models box, then consider the submeshes... @@ -753,14 +753,14 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe } // update triangles for picking { - FBXGeometry geometry; + HFMGeometry geometry; for (const auto& newMesh : meshes) { - FBXMesh mesh; + HFMMesh mesh; mesh._mesh = newMesh.getMeshPointer(); mesh.vertices = buffer_helpers::mesh::attributeToVector(mesh._mesh, gpu::Stream::POSITION); int numParts = (int)newMesh.getMeshPointer()->getNumParts(); for (int partID = 0; partID < numParts; partID++) { - FBXMeshPart part; + HFMMeshPart part; part.triangleIndices = buffer_helpers::bufferToVector(mesh._mesh->getIndexBuffer(), "part.triangleIndices"); mesh.parts << part; } @@ -789,12 +789,12 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { return result; } - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); int numberOfMeshes = geometry.meshes.size(); int shapeID = 0; for (int i = 0; i < numberOfMeshes; i++) { - const FBXMesh& fbxMesh = geometry.meshes.at(i); - if (auto mesh = fbxMesh._mesh) { + const HFMMesh& hfmMesh = geometry.meshes.at(i); + if (auto mesh = hfmMesh._mesh) { result.append(mesh); int numParts = (int)mesh->getNumParts(); @@ -808,7 +808,7 @@ scriptable::ScriptableModelBase Model::getScriptableModel() { return result; } -void Model::calculateTriangleSets(const FBXGeometry& geometry) { +void Model::calculateTriangleSets(const HFMGeometry& geometry) { PROFILE_RANGE(render, __FUNCTION__); int numberOfMeshes = geometry.meshes.size(); @@ -818,14 +818,14 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { _modelSpaceMeshTriangleSets.resize(numberOfMeshes); for (int i = 0; i < numberOfMeshes; i++) { - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = geometry.meshes.at(i); const int numberOfParts = mesh.parts.size(); auto& meshTriangleSets = _modelSpaceMeshTriangleSets[i]; meshTriangleSets.resize(numberOfParts); for (int j = 0; j < numberOfParts; j++) { - const FBXMeshPart& part = mesh.parts.at(j); + const HFMMeshPart& part = mesh.parts.at(j); auto& partTriangleSet = meshTriangleSets[j]; @@ -1114,7 +1114,7 @@ Extents Model::getBindExtents() const { if (!isActive()) { return Extents(); } - const Extents& bindExtents = getFBXGeometry().bindExtents; + const Extents& bindExtents = getHFMGeometry().bindExtents; Extents scaledExtents = { bindExtents.minimum * _scale, bindExtents.maximum * _scale }; return scaledExtents; } @@ -1128,12 +1128,12 @@ Extents Model::getMeshExtents() const { if (!isActive()) { return Extents(); } - const Extents& extents = getFBXGeometry().meshExtents; + const Extents& extents = getHFMGeometry().meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix - glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); - glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); + glm::vec3 minimum = glm::vec3(getHFMGeometry().offset * glm::vec4(extents.minimum, 1.0f)); + glm::vec3 maximum = glm::vec3(getHFMGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum * _scale, maximum * _scale }; return scaledExtents; } @@ -1143,12 +1143,12 @@ Extents Model::getUnscaledMeshExtents() const { return Extents(); } - const Extents& extents = getFBXGeometry().meshExtents; + const Extents& extents = getHFMGeometry().meshExtents; // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix - glm::vec3 minimum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); - glm::vec3 maximum = glm::vec3(getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); + glm::vec3 minimum = glm::vec3(getHFMGeometry().offset * glm::vec4(extents.minimum, 1.0f)); + glm::vec3 maximum = glm::vec3(getHFMGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum, maximum }; return scaledExtents; @@ -1171,11 +1171,11 @@ void Model::setJointTranslation(int index, bool valid, const glm::vec3& translat } int Model::getParentJointIndex(int jointIndex) const { - return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).parentIndex : -1; + return (isActive() && jointIndex != -1) ? getHFMGeometry().joints.at(jointIndex).parentIndex : -1; } int Model::getLastFreeJointIndex(int jointIndex) const { - return (isActive() && jointIndex != -1) ? getFBXGeometry().joints.at(jointIndex).freeLineage.last() : -1; + return (isActive() && jointIndex != -1) ? getHFMGeometry().joints.at(jointIndex).freeLineage.last() : -1; } void Model::setTextures(const QVariantMap& textures) { @@ -1275,7 +1275,7 @@ QStringList Model::getJointNames() const { Q_RETURN_ARG(QStringList, result)); return result; } - return isActive() ? getFBXGeometry().getJointNames() : QStringList(); + return isActive() ? getHFMGeometry().getJointNames() : QStringList(); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) { @@ -1415,12 +1415,12 @@ void Model::updateClusterMatrices() { } _needsUpdateClusterMatrices = false; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); + const HFMCluster& cluster = mesh.clusters.at(j); if (_useDualQuaternionSkinning) { auto jointPose = _rig.getJointPose(cluster.jointIndex); Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); @@ -1505,7 +1505,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& hfmGeometry = getHFMGeometry(); for (uint32_t i = 0; i < numMeshes; i++) { const auto& mesh = meshes.at(i); if (!mesh) { @@ -1515,7 +1515,7 @@ void Model::createRenderItemSet() { // Create the render payloads int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { - initializeBlendshapes(fbxGeometry.meshes[i], i); + initializeBlendshapes(hfmGeometry.meshes[i], i); _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); auto material = getGeometry()->getShapeMaterial(shapeID); _modelMeshMaterialNames.push_back(material ? material->getName() : ""); @@ -1600,7 +1600,7 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string class CollisionRenderGeometry : public Geometry { public: CollisionRenderGeometry(graphics::MeshPointer mesh) { - _fbxGeometry = std::make_shared(); + _hfmGeometry = std::make_shared(); std::shared_ptr meshes = std::make_shared(); meshes->push_back(mesh); _meshes = meshes; @@ -1656,9 +1656,9 @@ void Blender::run() { if (_model && _model->isLoaded()) { DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); int offset = 0; - auto meshes = _model->getFBXGeometry().meshes; + auto meshes = _model->getHFMGeometry().meshes; int meshIndex = 0; - foreach(const FBXMesh& mesh, meshes) { + foreach(const HFMMesh& mesh, meshes) { auto modelMeshBlendshapeOffsets = _model->_blendshapeOffsets.find(meshIndex++); if (mesh.blendshapes.isEmpty() || modelMeshBlendshapeOffsets == _model->_blendshapeOffsets.end()) { // Not blendshaped or not initialized @@ -1688,7 +1688,7 @@ void Blender::run() { } float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; - const FBXBlendshape& blendshape = mesh.blendshapes.at(i); + const HFMBlendshape& blendshape = mesh.blendshapes.at(i); tbb::parallel_for(tbb::blocked_range(0, blendshape.indices.size()), [&](const tbb::blocked_range& range) { for (auto j = range.begin(); j < range.end(); j++) { @@ -1731,7 +1731,7 @@ bool Model::maybeStartBlender() { return false; } -void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { +void Model::initializeBlendshapes(const HFMMesh& mesh, int index) { if (mesh.blendshapes.empty()) { // mesh doesn't have blendshape, did we allocate one though ? if (_blendshapeOffsets.find(index) != _blendshapeOffsets.end()) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 71809821eb..db5625b3d8 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -185,7 +185,7 @@ public: /// Provided as a convenience, will crash if !isLoaded() // And so that getGeometry() isn't chained everywhere - const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return _renderGeometry->getFBXGeometry(); } + const HFMGeometry& getHFMGeometry() const { assert(isLoaded()); return _renderGeometry->getHFMGeometry(); } bool isActive() const { return isLoaded(); } @@ -450,7 +450,7 @@ protected: bool _overrideModelTransform { false }; bool _triangleSetsValid { false }; - void calculateTriangleSets(const FBXGeometry& geometry); + void calculateTriangleSets(const HFMGeometry& geometry); std::vector> _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes virtual void createRenderItemSet(); @@ -506,7 +506,7 @@ protected: bool shouldInvalidatePayloadShapeKey(int meshIndex); - void initializeBlendshapes(const FBXMesh& mesh, int index); + void initializeBlendshapes(const HFMMesh& mesh, int index); private: float _loadingPriority { 0.0f }; diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 90015768d0..77b09caa1d 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -41,14 +41,14 @@ void SoftAttachmentModel::updateClusterMatrices() { _needsUpdateClusterMatrices = false; - const FBXGeometry& geometry = getFBXGeometry(); + const HFMGeometry& geometry = getHFMGeometry(); for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; - const FBXMesh& mesh = geometry.meshes.at(i); + const HFMMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { - const FBXCluster& cluster = mesh.clusters.at(j); + const HFMCluster& cluster = mesh.clusters.at(j); // TODO: cache these look-ups as an optimization int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); diff --git a/tests-manual/gpu/src/TestFbx.cpp b/tests-manual/gpu/src/TestFbx.cpp index 538bb0a973..ba94fb810c 100644 --- a/tests-manual/gpu/src/TestFbx.cpp +++ b/tests-manual/gpu/src/TestFbx.cpp @@ -100,7 +100,7 @@ bool TestFbx::isReady() const { void TestFbx::parseFbx(const QByteArray& fbxData) { QVariantHash mapping; - FBXGeometry* fbx = readFBX(fbxData, mapping); + HFMGeometry* fbx = readFBX(fbxData, mapping); size_t totalVertexCount = 0; size_t totalIndexCount = 0; size_t totalPartCount = 0; diff --git a/tests-manual/gpu/src/TestFbx.h b/tests-manual/gpu/src/TestFbx.h index 391fff1091..4e22928460 100644 --- a/tests-manual/gpu/src/TestFbx.h +++ b/tests-manual/gpu/src/TestFbx.h @@ -11,7 +11,7 @@ #include -class FBXGeometry; +class HFMGeometry; class TestFbx : public GpuTestBase { size_t _partCount { 0 }; diff --git a/tests/animation/src/AnimInverseKinematicsTests.cpp b/tests/animation/src/AnimInverseKinematicsTests.cpp index f5d3597f56..f51fe12ecb 100644 --- a/tests/animation/src/AnimInverseKinematicsTests.cpp +++ b/tests/animation/src/AnimInverseKinematicsTests.cpp @@ -28,8 +28,8 @@ const glm::quat identity = glm::quat(); const glm::quat quaterTurnAroundZ = glm::angleAxis(0.5f * PI, zAxis); -void makeTestFBXJoints(FBXGeometry& geometry) { - FBXJoint joint; +void makeTestFBXJoints(HFMGeometry& geometry) { + HFMJoint joint; joint.isFree = false; joint.freeLineage.clear(); joint.parentIndex = -1; @@ -79,7 +79,7 @@ void makeTestFBXJoints(FBXGeometry& geometry) { // compute each joint's transform for (int i = 1; i < (int)geometry.joints.size(); ++i) { - FBXJoint& j = geometry.joints[i]; + HFMJoint& j = geometry.joints[i]; int parentIndex = j.parentIndex; // World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1) j.transform = geometry.joints[parentIndex].transform * @@ -96,7 +96,7 @@ void AnimInverseKinematicsTests::testSingleChain() { AnimContext context(false, false, false, glm::mat4(), glm::mat4()); - FBXGeometry geometry; + HFMGeometry geometry; makeTestFBXJoints(geometry); // create a skeleton and doll diff --git a/tools/skeleton-dump/src/SkeletonDumpApp.cpp b/tools/skeleton-dump/src/SkeletonDumpApp.cpp index e9d8243e38..5107931da1 100644 --- a/tools/skeleton-dump/src/SkeletonDumpApp.cpp +++ b/tools/skeleton-dump/src/SkeletonDumpApp.cpp @@ -54,8 +54,8 @@ SkeletonDumpApp::SkeletonDumpApp(int argc, char* argv[]) : QCoreApplication(argc return; } QByteArray blob = file.readAll(); - std::unique_ptr fbxGeometry(readFBX(blob, QVariantHash())); - std::unique_ptr skeleton(new AnimSkeleton(*fbxGeometry)); + std::unique_ptr geometry(readFBX(blob, QVariantHash())); + std::unique_ptr skeleton(new AnimSkeleton(*geometry)); skeleton->dump(verbose); } diff --git a/tools/vhacd-util/src/VHACDUtil.cpp b/tools/vhacd-util/src/VHACDUtil.cpp index a52e948f01..bb2958e11d 100644 --- a/tools/vhacd-util/src/VHACDUtil.cpp +++ b/tools/vhacd-util/src/VHACDUtil.cpp @@ -19,16 +19,16 @@ // FBXReader jumbles the order of the meshes by reading them back out of a hashtable. This will put // them back in the order in which they appeared in the file. -bool FBXGeometryLessThan(const FBXMesh& e1, const FBXMesh& e2) { +bool HFMGeometryLessThan(const HFMMesh& e1, const HFMMesh& e2) { return e1.meshIndex < e2.meshIndex; } -void reSortFBXGeometryMeshes(FBXGeometry& geometry) { - qSort(geometry.meshes.begin(), geometry.meshes.end(), FBXGeometryLessThan); +void reSortHFMGeometryMeshes(HFMGeometry& geometry) { + qSort(geometry.meshes.begin(), geometry.meshes.end(), HFMGeometryLessThan); } // Read all the meshes from provided FBX file -bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { +bool vhacd::VHACDUtil::loadFBX(const QString filename, HFMGeometry& result) { if (_verbose) { qDebug() << "reading FBX file =" << filename << "..."; } @@ -41,7 +41,7 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } try { QByteArray fbxContents = fbx.readAll(); - FBXGeometry::Pointer geom; + HFMGeometry::Pointer geom; if (filename.toLower().endsWith(".obj")) { bool combineParts = false; geom = OBJReader().readOBJ(fbxContents, QVariantHash(), combineParts); @@ -53,7 +53,7 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } result = *geom; - reSortFBXGeometryMeshes(result); + reSortHFMGeometryMeshes(result); } catch (const QString& error) { qWarning() << "error reading" << filename << ":" << error; return false; @@ -63,7 +63,7 @@ bool vhacd::VHACDUtil::loadFBX(const QString filename, FBXGeometry& result) { } -void getTrianglesInMeshPart(const FBXMeshPart &meshPart, std::vector& triangleIndices) { +void getTrianglesInMeshPart(const HFMMeshPart &meshPart, std::vector& triangleIndices) { // append triangle indices triangleIndices.reserve(triangleIndices.size() + (size_t)meshPart.triangleIndices.size()); for (auto index : meshPart.triangleIndices) { @@ -88,12 +88,12 @@ void getTrianglesInMeshPart(const FBXMeshPart &meshPart, std::vector& trian } } -void vhacd::VHACDUtil::fattenMesh(const FBXMesh& mesh, const glm::mat4& geometryOffset, FBXMesh& result) const { +void vhacd::VHACDUtil::fattenMesh(const HFMMesh& mesh, const glm::mat4& geometryOffset, HFMMesh& result) const { // this is used to make meshes generated from a highfield collidable. each triangle // is converted into a tetrahedron and made into its own mesh-part. std::vector triangleIndices; - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { getTrianglesInMeshPart(meshPart, triangleIndices); } @@ -145,7 +145,7 @@ void vhacd::VHACDUtil::fattenMesh(const FBXMesh& mesh, const glm::mat4& geometry int index3 = result.vertices.size(); result.vertices << p3; // add the new point to the result mesh - FBXMeshPart newMeshPart; + HFMMeshPart newMeshPart; setMeshPartDefaults(newMeshPart, "unknown"); newMeshPart.triangleIndices << index0 << index1 << index2; newMeshPart.triangleIndices << index0 << index3 << index1; @@ -155,7 +155,7 @@ void vhacd::VHACDUtil::fattenMesh(const FBXMesh& mesh, const glm::mat4& geometry } } -AABox getAABoxForMeshPart(const FBXMesh& mesh, const FBXMeshPart &meshPart) { +AABox getAABoxForMeshPart(const HFMMesh& mesh, const HFMMeshPart &meshPart) { AABox aaBox; const int TRIANGLE_STRIDE = 3; for (int i = 0; i < meshPart.triangleIndices.size(); i += TRIANGLE_STRIDE) { @@ -242,7 +242,7 @@ bool isClosedManifold(const std::vector& triangleIndices) { return true; } -void vhacd::VHACDUtil::getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& resultMesh) const { +void vhacd::VHACDUtil::getConvexResults(VHACD::IVHACD* convexifier, HFMMesh& resultMesh) const { // Number of hulls for this input meshPart uint32_t numHulls = convexifier->GetNConvexHulls(); if (_verbose) { @@ -256,8 +256,8 @@ void vhacd::VHACDUtil::getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& res VHACD::IVHACD::ConvexHull hull; convexifier->GetConvexHull(j, hull); - resultMesh.parts.append(FBXMeshPart()); - FBXMeshPart& resultMeshPart = resultMesh.parts.last(); + resultMesh.parts.append(HFMMeshPart()); + HFMMeshPart& resultMeshPart = resultMesh.parts.last(); int hullIndexStart = resultMesh.vertices.size(); resultMesh.vertices.reserve(hullIndexStart + hull.m_nPoints); @@ -288,9 +288,9 @@ float computeDt(uint64_t start) { return (float)(usecTimestampNow() - start) / (float)USECS_PER_SECOND; } -bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, +bool vhacd::VHACDUtil::computeVHACD(HFMGeometry& geometry, VHACD::IVHACD::Parameters params, - FBXGeometry& result, + HFMGeometry& result, float minimumMeshSize, float maximumMeshSize) { if (_verbose) { qDebug() << "meshes =" << geometry.meshes.size(); @@ -298,7 +298,7 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, // count the mesh-parts int numParts = 0; - foreach (const FBXMesh& mesh, geometry.meshes) { + foreach (const HFMMesh& mesh, geometry.meshes) { numParts += mesh.parts.size(); } if (_verbose) { @@ -308,15 +308,15 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, VHACD::IVHACD * convexifier = VHACD::CreateVHACD(); result.meshExtents.reset(); - result.meshes.append(FBXMesh()); - FBXMesh &resultMesh = result.meshes.last(); + result.meshes.append(HFMMesh()); + HFMMesh &resultMesh = result.meshes.last(); const uint32_t POINT_STRIDE = 3; const uint32_t TRIANGLE_STRIDE = 3; int meshIndex = 0; int validPartsFound = 0; - foreach (const FBXMesh& mesh, geometry.meshes) { + foreach (const HFMMesh& mesh, geometry.meshes) { // find duplicate points int numDupes = 0; @@ -354,7 +354,7 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, int partIndex = 0; std::vector triangleIndices; - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { triangleIndices.clear(); getTrianglesInMeshPart(meshPart, triangleIndices); @@ -421,7 +421,7 @@ bool vhacd::VHACDUtil::computeVHACD(FBXGeometry& geometry, triangleIndices.clear(); for (auto index : openParts) { - const FBXMeshPart &meshPart = mesh.parts[index]; + const HFMMeshPart &meshPart = mesh.parts[index]; getTrianglesInMeshPart(meshPart, triangleIndices); } diff --git a/tools/vhacd-util/src/VHACDUtil.h b/tools/vhacd-util/src/VHACDUtil.h index 35ec3ef56b..64e86ed7df 100644 --- a/tools/vhacd-util/src/VHACDUtil.h +++ b/tools/vhacd-util/src/VHACDUtil.h @@ -27,16 +27,16 @@ namespace vhacd { public: void setVerbose(bool verbose) { _verbose = verbose; } - bool loadFBX(const QString filename, FBXGeometry& result); + bool loadFBX(const QString filename, HFMGeometry& result); - void fattenMesh(const FBXMesh& mesh, const glm::mat4& gometryOffset, FBXMesh& result) const; + void fattenMesh(const HFMMesh& mesh, const glm::mat4& gometryOffset, HFMMesh& result) const; - bool computeVHACD(FBXGeometry& geometry, + bool computeVHACD(HFMGeometry& geometry, VHACD::IVHACD::Parameters params, - FBXGeometry& result, + HFMGeometry& result, float minimumMeshSize, float maximumMeshSize); - void getConvexResults(VHACD::IVHACD* convexifier, FBXMesh& resultMesh) const; + void getConvexResults(VHACD::IVHACD* convexifier, HFMMesh& resultMesh) const; ~VHACDUtil(); @@ -55,6 +55,6 @@ namespace vhacd { }; } -AABox getAABoxForMeshPart(const FBXMeshPart &meshPart); +AABox getAABoxForMeshPart(const HFMMeshPart &meshPart); #endif //hifi_VHACDUtil_h diff --git a/tools/vhacd-util/src/VHACDUtilApp.cpp b/tools/vhacd-util/src/VHACDUtilApp.cpp index c263dce609..0941198234 100644 --- a/tools/vhacd-util/src/VHACDUtilApp.cpp +++ b/tools/vhacd-util/src/VHACDUtilApp.cpp @@ -36,7 +36,7 @@ QString formatFloat(double n) { } -bool VHACDUtilApp::writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart) { +bool VHACDUtilApp::writeOBJ(QString outFileName, HFMGeometry& geometry, bool outputCentimeters, int whichMeshPart) { QFile file(outFileName); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "unable to write to" << outFileName; @@ -56,9 +56,9 @@ bool VHACDUtilApp::writeOBJ(QString outFileName, FBXGeometry& geometry, bool out int vertexIndexOffset = 0; - foreach (const FBXMesh& mesh, geometry.meshes) { + foreach (const HFMMesh& mesh, geometry.meshes) { bool verticesHaveBeenOutput = false; - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { if (whichMeshPart >= 0 && nth != (unsigned int) whichMeshPart) { nth++; continue; @@ -297,7 +297,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : } // load the mesh - FBXGeometry fbx; + HFMGeometry fbx; auto begin = std::chrono::high_resolution_clock::now(); if (!vUtil.loadFBX(inputFilename, fbx)){ _returnCode = VHACD_RETURN_CODE_FAILURE_TO_READ; @@ -315,8 +315,8 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : QVector infileExtensions = {"fbx", "obj"}; QString baseFileName = fileNameWithoutExtension(outputFilename, infileExtensions); int count = 0; - foreach (const FBXMesh& mesh, fbx.meshes) { - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMesh& mesh, fbx.meshes) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { QString outputFileName = baseFileName + "-" + QString::number(count) + ".obj"; writeOBJ(outputFileName, fbx, outputCentimeters, count); count++; @@ -358,7 +358,7 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : } begin = std::chrono::high_resolution_clock::now(); - FBXGeometry result; + HFMGeometry result; bool success = vUtil.computeVHACD(fbx, params, result, minimumMeshSize, maximumMeshSize); end = std::chrono::high_resolution_clock::now(); @@ -377,9 +377,9 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : int totalVertices = 0; int totalTriangles = 0; - foreach (const FBXMesh& mesh, result.meshes) { + foreach (const HFMMesh& mesh, result.meshes) { totalVertices += mesh.vertices.size(); - foreach (const FBXMeshPart &meshPart, mesh.parts) { + foreach (const HFMMeshPart &meshPart, mesh.parts) { totalTriangles += meshPart.triangleIndices.size() / 3; // each quad was made into two triangles totalTriangles += 2 * meshPart.quadIndices.size() / 4; @@ -398,17 +398,17 @@ VHACDUtilApp::VHACDUtilApp(int argc, char* argv[]) : } if (fattenFaces) { - FBXGeometry newFbx; - FBXMesh result; + HFMGeometry newFbx; + HFMMesh result; // count the mesh-parts unsigned int meshCount = 0; - foreach (const FBXMesh& mesh, fbx.meshes) { + foreach (const HFMMesh& mesh, fbx.meshes) { meshCount += mesh.parts.size(); } result.modelTransform = glm::mat4(); // Identity matrix - foreach (const FBXMesh& mesh, fbx.meshes) { + foreach (const HFMMesh& mesh, fbx.meshes) { vUtil.fattenMesh(mesh, fbx.offset, result); } diff --git a/tools/vhacd-util/src/VHACDUtilApp.h b/tools/vhacd-util/src/VHACDUtilApp.h index 0d75275802..3db49456a0 100644 --- a/tools/vhacd-util/src/VHACDUtilApp.h +++ b/tools/vhacd-util/src/VHACDUtilApp.h @@ -28,7 +28,7 @@ public: VHACDUtilApp(int argc, char* argv[]); ~VHACDUtilApp(); - bool writeOBJ(QString outFileName, FBXGeometry& geometry, bool outputCentimeters, int whichMeshPart = -1); + bool writeOBJ(QString outFileName, HFMGeometry& geometry, bool outputCentimeters, int whichMeshPart = -1); int getReturnCode() const { return _returnCode; } From 41ec026f8d521d10f10f9f27718ffb65f1462cbd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 29 Oct 2018 16:16:16 -0700 Subject: [PATCH 266/276] cleanup packet times, avoid ambiguous RTT calcs, allow fast re-transmit --- .../networking/src/udt/CongestionControl.h | 2 + libraries/networking/src/udt/Connection.cpp | 22 +- libraries/networking/src/udt/SendQueue.cpp | 1 + libraries/networking/src/udt/TCPVegasCC.cpp | 246 +++++++++++------- libraries/networking/src/udt/TCPVegasCC.h | 19 +- 5 files changed, 177 insertions(+), 113 deletions(-) diff --git a/libraries/networking/src/udt/CongestionControl.h b/libraries/networking/src/udt/CongestionControl.h index 7093e8bd96..bfe7f552d1 100644 --- a/libraries/networking/src/udt/CongestionControl.h +++ b/libraries/networking/src/udt/CongestionControl.h @@ -45,8 +45,10 @@ public: virtual void onTimeout() {} virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) {} + virtual void onPacketReSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) {} virtual int estimatedTimeout() const = 0; + protected: void setMSS(int mss) { _mss = mss; } virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) = 0; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 24e294881a..4798288a18 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -195,7 +195,7 @@ void Connection::recordSentPackets(int wireSize, int payloadSize, void Connection::recordRetransmission(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { _stats.record(ConnectionStats::Stats::Retransmission); - _congestionControl->onPacketSent(wireSize, seqNum, timePoint); + _congestionControl->onPacketReSent(wireSize, seqNum, timePoint); } void Connection::sendACK() { @@ -303,7 +303,7 @@ void Connection::processControl(ControlPacketPointer controlPacket) { // where the other end expired our connection. Let's reset. #ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Got HandshakeRequest from" << _destination << ", stopping SendQueue"; + qCDebug(networking) << "Got HandshakeRequest from" << _destination << ", stopping SendQueue"; #endif _hasReceivedHandshakeACK = false; stopSendQueue(); @@ -327,19 +327,19 @@ void Connection::processACK(ControlPacketPointer controlPacket) { return; } - if (ack <= _lastReceivedACK) { + if (ack < _lastReceivedACK) { // this is an out of order ACK, bail - // or - // processing an already received ACK, bail return; } - - _lastReceivedACK = ack; - - // ACK the send queue so it knows what was received - getSendQueue().ack(ack); - + if (ack > _lastReceivedACK) { + // this is not a repeated ACK, so update our member and tell the send queue + _lastReceivedACK = ack; + + // ACK the send queue so it knows what was received + getSendQueue().ack(ack); + } + // give this ACK to the congestion control and update the send queue parameters updateCongestionControlAndSendQueue([this, ack, &controlPacket] { if (_congestionControl->onACK(ack, controlPacket->getReceiveTime())) { diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index b1dfb9a8cf..9cba4970ac 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -481,6 +481,7 @@ bool SendQueue::isInactive(bool attemptedToSendPacket) { auto cvStatus = _emptyCondition.wait_for(locker, EMPTY_QUEUES_INACTIVE_TIMEOUT); if (cvStatus == std::cv_status::timeout && (_packets.isEmpty() || isFlowWindowFull()) && _naks.isEmpty()) { + #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "SendQueue to" << _destination << "has been empty for" << EMPTY_QUEUES_INACTIVE_TIMEOUT.count() diff --git a/libraries/networking/src/udt/TCPVegasCC.cpp b/libraries/networking/src/udt/TCPVegasCC.cpp index 4842e5a204..f2119237c2 100644 --- a/libraries/networking/src/udt/TCPVegasCC.cpp +++ b/libraries/networking/src/udt/TCPVegasCC.cpp @@ -27,112 +27,106 @@ TCPVegasCC::TCPVegasCC() { _baseRTT = std::numeric_limits::max(); } +bool TCPVegasCC::calculateRTT(p_high_resolution_clock::time_point sendTime, p_high_resolution_clock::time_point receiveTime) { + // calculate the RTT (receive time - time ACK sent) + int lastRTT = duration_cast(receiveTime - sendTime).count(); + + const int MAX_RTT_SAMPLE_MICROSECONDS = 10000000; + + if (lastRTT < 0) { + Q_ASSERT_X(false, __FUNCTION__, "calculated an RTT that is not > 0"); + return false; + } else if (lastRTT == 0) { + // we do not allow a zero microsecond RTT (as per the UNIX kernel implementation of TCP Vegas) + lastRTT = 1; + } else if (lastRTT > MAX_RTT_SAMPLE_MICROSECONDS) { + // we cap the lastRTT to MAX_RTT_SAMPLE_MICROSECONDS to avoid overflows in window size calculations + lastRTT = MAX_RTT_SAMPLE_MICROSECONDS; + } + + if (_ewmaRTT == -1) { + // first RTT sample - set _ewmaRTT to the value and set the variance to half the value + _ewmaRTT = lastRTT; + _rttVariance = lastRTT / 2; + } else { + // This updates the RTT using exponential weighted moving average + // This is the Jacobson's forumla for RTT estimation + // http://www.mathcs.emory.edu/~cheung/Courses/455/Syllabus/7-transport/Jacobson-88.pdf + + // Estimated RTT = (1 - x)(estimatedRTT) + (x)(sampleRTT) + // (where x = 0.125 via Jacobson) + + // Deviation = (1 - x)(deviation) + x |sampleRTT - estimatedRTT| + // (where x = 0.25 via Jacobson) + + static const int RTT_ESTIMATION_ALPHA = 8; + static const int RTT_ESTIMATION_VARIANCE_ALPHA = 4; + + _ewmaRTT = (_ewmaRTT * (RTT_ESTIMATION_ALPHA - 1) + lastRTT) / RTT_ESTIMATION_ALPHA; + _rttVariance = (_rttVariance * (RTT_ESTIMATION_VARIANCE_ALPHA- 1) + + abs(lastRTT - _ewmaRTT)) / RTT_ESTIMATION_VARIANCE_ALPHA; + } + + // keep track of the lowest RTT during connection + _baseRTT = std::min(_baseRTT, lastRTT); + + // find the min RTT during the last RTT + _currentMinRTT = std::min(_currentMinRTT, lastRTT); + + // add 1 to the number of RTT samples collected during this RTT window + ++_numRTTs; + + return true; +} + bool TCPVegasCC::onACK(SequenceNumber ack, p_high_resolution_clock::time_point receiveTime) { - auto it = _sentPacketTimes.find(ack); auto previousAck = _lastACK; _lastACK = ack; - if (it != _sentPacketTimes.end()) { + bool wasDuplicateACK = (ack == previousAck); - // calculate the RTT (receive time - time ACK sent) - int lastRTT = duration_cast(receiveTime - it->second).count(); + auto it = std::find_if(_sentPacketDatas.begin(), _sentPacketDatas.end(), [ack](SentPacketData& packetTime){ + return packetTime.sequenceNumber == ack; + }); - const int MAX_RTT_SAMPLE_MICROSECONDS = 10000000; + if (!wasDuplicateACK && it != _sentPacketDatas.end()) { + // check if we can unambigiously calculate an RTT from this ACK - if (lastRTT < 0) { - Q_ASSERT_X(false, __FUNCTION__, "calculated an RTT that is not > 0"); + // for that to be the case, + // any of the packets this ACK covers (from the current ACK back to our previous ACK) + // must not have been re-sent + bool canBeUsedForRTT = std::none_of(_sentPacketDatas.begin(), _sentPacketDatas.end(), + [ack, previousAck](SentPacketData& sentPacketData) + { + return sentPacketData.sequenceNumber > previousAck + && sentPacketData.sequenceNumber <= ack + && sentPacketData.wasResent; + }); + + auto sendTime = it->timePoint; + + // remove all sent packet times up to this sequence number + it = _sentPacketDatas.erase(_sentPacketDatas.begin(), it + 1); + + // if we can use this ACK for an RTT calculation then do so + // returning false if we calculate an invalid RTT + if (canBeUsedForRTT && !calculateRTT(sendTime, receiveTime)) { return false; - } else if (lastRTT == 0) { - // we do not allow a zero microsecond RTT (as per the UNIX kernel implementation of TCP Vegas) - lastRTT = 1; - } else if (lastRTT > MAX_RTT_SAMPLE_MICROSECONDS) { - // we cap the lastRTT to MAX_RTT_SAMPLE_MICROSECONDS to avoid overflows in window size calculations - lastRTT = MAX_RTT_SAMPLE_MICROSECONDS; } + } - if (_ewmaRTT == -1) { - // first RTT sample - set _ewmaRTT to the value and set the variance to half the value - _ewmaRTT = lastRTT; - _rttVariance = lastRTT / 2; - } else { - // This updates the RTT using exponential weighted moving average - // This is the Jacobson's forumla for RTT estimation - // http://www.mathcs.emory.edu/~cheung/Courses/455/Syllabus/7-transport/Jacobson-88.pdf - - // Estimated RTT = (1 - x)(estimatedRTT) + (x)(sampleRTT) - // (where x = 0.125 via Jacobson) - - // Deviation = (1 - x)(deviation) + x |sampleRTT - estimatedRTT| - // (where x = 0.25 via Jacobson) - - static const int RTT_ESTIMATION_ALPHA = 8; - static const int RTT_ESTIMATION_VARIANCE_ALPHA = 4; - - _ewmaRTT = (_ewmaRTT * (RTT_ESTIMATION_ALPHA - 1) + lastRTT) / RTT_ESTIMATION_ALPHA; - _rttVariance = (_rttVariance * (RTT_ESTIMATION_VARIANCE_ALPHA- 1) - + abs(lastRTT - _ewmaRTT)) / RTT_ESTIMATION_VARIANCE_ALPHA; - } - - // add 1 to the number of ACKs during this RTT - ++_numACKs; - - // keep track of the lowest RTT during connection - _baseRTT = std::min(_baseRTT, lastRTT); - - // find the min RTT during the last RTT - _currentMinRTT = std::min(_currentMinRTT, lastRTT); - - auto sinceLastAdjustment = duration_cast(p_high_resolution_clock::now() - _lastAdjustmentTime).count(); - if (sinceLastAdjustment >= _ewmaRTT) { - performCongestionAvoidance(ack); - } - - // remove this sent packet time from the hash - _sentPacketTimes.erase(it); + auto sinceLastAdjustment = duration_cast(p_high_resolution_clock::now() - _lastAdjustmentTime).count(); + if (sinceLastAdjustment >= _ewmaRTT) { + performCongestionAvoidance(ack); } ++_numACKSinceFastRetransmit; // perform the fast re-transmit check if this is a duplicate ACK or if this is the first or second ACK // after a previous fast re-transmit - if (ack == previousAck || _numACKSinceFastRetransmit < 3) { - // we may need to re-send ackNum + 1 if it has been more than our estimated timeout since it was sent - - auto it = _sentPacketTimes.find(ack + 1); - if (it != _sentPacketTimes.end()) { - - auto now = p_high_resolution_clock::now(); - auto sinceSend = duration_cast(now - it->second).count(); - - if (sinceSend >= estimatedTimeout()) { - // break out of slow start, we've decided this is loss - _slowStart = false; - - // reset the fast re-transmit counter - _numACKSinceFastRetransmit = 0; - - // return true so the caller knows we needed a fast re-transmit - return true; - } - } - - // if this is the 3rd duplicate ACK, we fallback to Reno's fast re-transmit - static const int RENO_FAST_RETRANSMIT_DUPLICATE_COUNT = 3; - - ++_duplicateACKCount; - - if (ack == previousAck && _duplicateACKCount == RENO_FAST_RETRANSMIT_DUPLICATE_COUNT) { - // break out of slow start, we just hit loss - _slowStart = false; - - // reset our fast re-transmit counters - _numACKSinceFastRetransmit = 0; - _duplicateACKCount = 0; - - // return true so the caller knows we needed a fast re-transmit - return true; - } + if (wasDuplicateACK || _numACKSinceFastRetransmit < 3) { + return needsFastRetransmit(ack, wasDuplicateACK); } else { _duplicateACKCount = 0; } @@ -141,6 +135,49 @@ bool TCPVegasCC::onACK(SequenceNumber ack, p_high_resolution_clock::time_point r return false; } +bool TCPVegasCC::needsFastRetransmit(SequenceNumber ack, bool wasDuplicateACK) { + // we may need to re-send ackNum + 1 if it has been more than our estimated timeout since it was sent + + auto nextIt = std::find_if(_sentPacketDatas.begin(), _sentPacketDatas.end(), [ack](SentPacketData& packetTime){ + return packetTime.sequenceNumber == ack + 1; + }); + + if (nextIt != _sentPacketDatas.end()) { + auto now = p_high_resolution_clock::now(); + auto sinceSend = duration_cast(now - nextIt->timePoint).count(); + + if (sinceSend >= estimatedTimeout()) { + // break out of slow start, we've decided this is loss + _slowStart = false; + + // reset the fast re-transmit counter + _numACKSinceFastRetransmit = 0; + + // return true so the caller knows we needed a fast re-transmit + return true; + } + } + + // if this is the 3rd duplicate ACK, we fallback to Reno's fast re-transmit + static const int RENO_FAST_RETRANSMIT_DUPLICATE_COUNT = 3; + + ++_duplicateACKCount; + + if (wasDuplicateACK && _duplicateACKCount == RENO_FAST_RETRANSMIT_DUPLICATE_COUNT) { + // break out of slow start, we just hit loss + _slowStart = false; + + // reset our fast re-transmit counters + _numACKSinceFastRetransmit = 0; + _duplicateACKCount = 0; + + // return true so the caller knows we needed a fast re-transmit + return true; + } + + return false; +} + void TCPVegasCC::performCongestionAvoidance(udt::SequenceNumber ack) { static int VEGAS_ALPHA_SEGMENTS = 4; static int VEGAS_BETA_SEGMENTS = 6; @@ -158,7 +195,7 @@ void TCPVegasCC::performCongestionAvoidance(udt::SequenceNumber ack) { int64_t windowSizeDiff = (int64_t) _congestionWindowSize * (rtt - _baseRTT) / _baseRTT; - if (_numACKs <= 2) { + if (_numRTTs <= 2) { performRenoCongestionAvoidance(ack); } else { if (_slowStart) { @@ -209,7 +246,7 @@ void TCPVegasCC::performCongestionAvoidance(udt::SequenceNumber ack) { _currentMinRTT = std::numeric_limits::max(); // reset our count of collected RTT samples - _numACKs = 0; + _numRTTs = 0; } @@ -230,29 +267,29 @@ void TCPVegasCC::performRenoCongestionAvoidance(SequenceNumber ack) { return; } - int numAcked = _numACKs; + int numRTTCollected = _numRTTs; if (_slowStart) { // while in slow start we grow the congestion window by the number of ACKed packets // allowing it to grow as high as the slow start threshold - int congestionWindow = _congestionWindowSize + numAcked; + int congestionWindow = _congestionWindowSize + numRTTCollected; if (congestionWindow > udt::MAX_PACKETS_IN_FLIGHT) { // we're done with slow start, set the congestion window to the slow start threshold _congestionWindowSize = udt::MAX_PACKETS_IN_FLIGHT; // figure out how many left over ACKs we should apply using the regular reno congestion avoidance - numAcked = congestionWindow - udt::MAX_PACKETS_IN_FLIGHT; + numRTTCollected = congestionWindow - udt::MAX_PACKETS_IN_FLIGHT; } else { _congestionWindowSize = congestionWindow; - numAcked = 0; + numRTTCollected = 0; } } // grab the size of the window prior to reno additive increase int preAIWindowSize = _congestionWindowSize; - if (numAcked > 0) { + if (numRTTCollected > 0) { // Once we are out of slow start, we use additive increase to grow the window slowly. // We grow the congestion window by a single packet everytime the entire congestion window is sent. @@ -263,7 +300,7 @@ void TCPVegasCC::performRenoCongestionAvoidance(SequenceNumber ack) { } // increase the window size by (1 / window size) for every ACK received - _ackAICount += numAcked; + _ackAICount += numRTTCollected; if (_ackAICount >= preAIWindowSize) { // when _ackAICount % preAIWindowSize == 0 then _ackAICount is 0 // when _ackAICount % preAIWindowSize != 0 then _ackAICount is _ackAICount - (_ackAICount % preAIWindowSize) @@ -277,8 +314,19 @@ void TCPVegasCC::performRenoCongestionAvoidance(SequenceNumber ack) { } void TCPVegasCC::onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { - if (_sentPacketTimes.find(seqNum) == _sentPacketTimes.end()) { - _sentPacketTimes[seqNum] = timePoint; + _sentPacketDatas.emplace_back(seqNum, timePoint); +} + +void TCPVegasCC::onPacketReSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { + // look for our information for this sent packet + auto it = std::find_if(_sentPacketDatas.begin(), _sentPacketDatas.end(), [seqNum](SentPacketData& sentPacketInfo){ + return sentPacketInfo.sequenceNumber == seqNum; + }); + + // if we found information for this packet (it hasn't been erased because it hasn't yet been ACKed) + // then mark it as re-sent so we know it cannot be used for RTT calculations + if (it != _sentPacketDatas.end()) { + it->wasResent = true; } } diff --git a/libraries/networking/src/udt/TCPVegasCC.h b/libraries/networking/src/udt/TCPVegasCC.h index bb14728d4b..1d83c4c992 100644 --- a/libraries/networking/src/udt/TCPVegasCC.h +++ b/libraries/networking/src/udt/TCPVegasCC.h @@ -30,6 +30,7 @@ public: virtual void onTimeout() override {}; virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) override; + virtual void onPacketReSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) override; virtual int estimatedTimeout() const override; @@ -37,11 +38,23 @@ protected: virtual void performCongestionAvoidance(SequenceNumber ack); virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) override { _lastACK = seqNum - 1; } private: + bool calculateRTT(p_high_resolution_clock::time_point sendTime, p_high_resolution_clock::time_point receiveTime); + bool needsFastRetransmit(SequenceNumber ack, bool wasDuplicateACK); + bool isCongestionWindowLimited(); void performRenoCongestionAvoidance(SequenceNumber ack); - using PacketTimeList = std::map; - PacketTimeList _sentPacketTimes; // Map of sequence numbers to sent time + struct SentPacketData { + SentPacketData(SequenceNumber seqNum, p_high_resolution_clock::time_point tPoint) + : sequenceNumber(seqNum), timePoint(tPoint) {}; + + SequenceNumber sequenceNumber; + p_high_resolution_clock::time_point timePoint; + bool wasResent { false }; + }; + + using PacketTimeList = std::vector; + PacketTimeList _sentPacketDatas; // association of sequence numbers to sent time, for RTT calc p_high_resolution_clock::time_point _lastAdjustmentTime; // Time of last congestion control adjustment @@ -56,7 +69,7 @@ private: int _ewmaRTT { -1 }; // Exponential weighted moving average RTT int _rttVariance { 0 }; // Variance in collected RTT values - int _numACKs { 0 }; // Number of ACKs received during the last RTT (since last performed congestion avoidance) + int _numRTTs { 0 }; // Number of RTTs calculated during the last RTT (since last performed congestion avoidance) int _ackAICount { 0 }; // Counter for number of ACKs received for Reno additive increase int _duplicateACKCount { 0 }; // Counter for duplicate ACKs received From 0b7ddca5f66058e5df4fe7d356362f1358dcfcc7 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Wed, 31 Oct 2018 14:03:20 -0700 Subject: [PATCH 267/276] Change naming for straggler names in model loading debug dumps --- libraries/fbx/src/GLTFReader.cpp | 60 ++++++++++++++++---------------- libraries/fbx/src/GLTFReader.h | 2 +- libraries/fbx/src/OBJReader.cpp | 18 +++++----- libraries/fbx/src/OBJReader.h | 2 +- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp index 05534b5264..7ee13c5cdf 100644 --- a/libraries/fbx/src/GLTFReader.cpp +++ b/libraries/fbx/src/GLTFReader.cpp @@ -929,7 +929,7 @@ HFMGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping buildGeometry(geometry, url); - //fbxDebugDump(geometry); + //hfmDebugDump(geometry); return geometryPtr; } @@ -1181,37 +1181,37 @@ void GLTFReader::retriangulate(const QVector& inIndices, const QVector Date: Wed, 31 Oct 2018 14:15:30 -0700 Subject: [PATCH 268/276] Change local variable in TestFbx for clarity --- tests-manual/gpu/src/TestFbx.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests-manual/gpu/src/TestFbx.cpp b/tests-manual/gpu/src/TestFbx.cpp index ba94fb810c..9890e4fbe7 100644 --- a/tests-manual/gpu/src/TestFbx.cpp +++ b/tests-manual/gpu/src/TestFbx.cpp @@ -100,12 +100,12 @@ bool TestFbx::isReady() const { void TestFbx::parseFbx(const QByteArray& fbxData) { QVariantHash mapping; - HFMGeometry* fbx = readFBX(fbxData, mapping); + HFMGeometry* geometry = readFBX(fbxData, mapping); size_t totalVertexCount = 0; size_t totalIndexCount = 0; size_t totalPartCount = 0; size_t highestIndex = 0; - for (const auto& mesh : fbx->meshes) { + for (const auto& mesh : geometry->meshes) { size_t vertexCount = mesh.vertices.size(); totalVertexCount += mesh.vertices.size(); highestIndex = std::max(highestIndex, vertexCount); @@ -123,7 +123,7 @@ void TestFbx::parseFbx(const QByteArray& fbxData) { std::vector parts; parts.reserve(totalPartCount); _partCount = totalPartCount; - for (const auto& mesh : fbx->meshes) { + for (const auto& mesh : geometry->meshes) { baseVertex = vertices.size(); vec3 color; @@ -133,7 +133,7 @@ void TestFbx::parseFbx(const QByteArray& fbxData) { partIndirect.firstIndex = (uint)indices.size(); partIndirect.baseInstance = (uint)parts.size(); _partTransforms.push_back(mesh.modelTransform); - auto material = fbx->materials[part.materialID]; + auto material = geometry->materials[part.materialID]; color = material.diffuseColor; for (auto index : part.quadTrianglesIndices) { indices.push_back(index); @@ -163,7 +163,7 @@ void TestFbx::parseFbx(const QByteArray& fbxData) { _vertexBuffer->append(vertices); _indexBuffer->append(indices); _indirectBuffer->append(parts); - delete fbx; + delete geometry; } void TestFbx::renderTest(size_t testId, RenderArgs* args) { From 41a0d093896cc4c08b103a92d8473bc689f598d2 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 31 Oct 2018 15:31:51 -0700 Subject: [PATCH 269/276] First pass --- .../qml/LoginDialog/LinkAccountBody.qml | 14 +- .../hifi/commerce/common/CommerceLightbox.qml | 8 +- .../qml/hifi/commerce/wallet/Security.qml | 246 -------------- .../qml/hifi/dialogs/security/Security.qml | 306 ++++++++++++++++++ .../security}/SecurityImageChange.qml | 32 +- .../security}/SecurityImageModel.qml | 8 +- .../security}/SecurityImageSelection.qml | 16 +- .../hifi/dialogs/security/SecurityWrapper.qml | 30 ++ .../wallet => dialogs/security}/images/01.jpg | Bin .../wallet => dialogs/security}/images/02.jpg | Bin .../wallet => dialogs/security}/images/03.jpg | Bin .../wallet => dialogs/security}/images/04.jpg | Bin .../wallet => dialogs/security}/images/05.jpg | Bin .../wallet => dialogs/security}/images/06.jpg | Bin .../wallet => dialogs/security}/images/07.jpg | Bin .../wallet => dialogs/security}/images/08.jpg | Bin .../wallet => dialogs/security}/images/09.jpg | Bin .../wallet => dialogs/security}/images/10.jpg | Bin .../wallet => dialogs/security}/images/11.jpg | Bin .../wallet => dialogs/security}/images/12.jpg | Bin .../wallet => dialogs/security}/images/13.jpg | Bin .../wallet => dialogs/security}/images/14.jpg | Bin .../wallet => dialogs/security}/images/15.jpg | Bin .../wallet => dialogs/security}/images/16.jpg | Bin .../wallet => dialogs/security}/images/17.jpg | Bin .../wallet => dialogs/security}/images/18.jpg | Bin .../wallet => dialogs/security}/images/19.jpg | Bin .../wallet => dialogs/security}/images/20.jpg | Bin .../wallet => dialogs/security}/images/21.jpg | Bin .../wallet => dialogs/security}/images/22.jpg | Bin .../wallet => dialogs/security}/images/23.jpg | Bin .../wallet => dialogs/security}/images/24.jpg | Bin .../wallet => dialogs/security}/images/25.jpg | Bin .../wallet => dialogs/security}/images/26.jpg | Bin .../wallet => dialogs/security}/images/27.jpg | Bin .../wallet => dialogs/security}/images/28.jpg | Bin .../wallet => dialogs/security}/images/29.jpg | Bin .../wallet => dialogs/security}/images/30.jpg | Bin .../wallet => dialogs/security}/images/31.jpg | Bin .../wallet => dialogs/security}/images/32.jpg | Bin .../wallet => dialogs/security}/images/33.jpg | Bin .../wallet => dialogs/security}/images/34.jpg | Bin .../security}/images/lowerKeyboard.png | Bin .../security}/images/wallet-bg.jpg | Bin .../security}/images/wallet-tip-bg.png | Bin interface/src/Application.cpp | 12 +- interface/src/Menu.cpp | 7 + scripts/system/commerce/wallet.js | 6 +- 48 files changed, 388 insertions(+), 297 deletions(-) delete mode 100644 interface/resources/qml/hifi/commerce/wallet/Security.qml create mode 100644 interface/resources/qml/hifi/dialogs/security/Security.qml rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/SecurityImageChange.qml (89%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/SecurityImageModel.qml (89%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/SecurityImageSelection.qml (88%) create mode 100644 interface/resources/qml/hifi/dialogs/security/SecurityWrapper.qml rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/01.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/02.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/03.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/04.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/05.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/06.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/07.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/08.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/09.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/10.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/11.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/12.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/13.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/14.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/15.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/16.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/17.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/18.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/19.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/20.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/21.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/22.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/23.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/24.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/25.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/26.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/27.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/28.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/29.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/30.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/31.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/32.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/33.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/34.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/lowerKeyboard.png (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/wallet-bg.jpg (100%) rename interface/resources/qml/hifi/{commerce/wallet => dialogs/security}/images/wallet-tip-bg.png (100%) diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 48cf124127..d5d89cd0b4 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -136,7 +136,7 @@ Item { TextField { id: usernameField - text: Settings.getValue("wallet/savedUsername", ""); + text: Settings.getValue("keepMeLoggedIn/savedUsername", ""); width: parent.width focus: true placeholderText: "Username or Email" @@ -165,7 +165,7 @@ Item { root.text = ""; } Component.onCompleted: { - var savedUsername = Settings.getValue("wallet/savedUsername", ""); + var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", ""); usernameField.text = savedUsername === "Unknown user" ? "" : savedUsername; } } @@ -263,21 +263,21 @@ Item { CheckBox { id: autoLogoutCheckbox - checked: !Settings.getValue("wallet/autoLogout", true) + checked: Settings.getValue("keepMeLoggedIn", false) text: "Keep me signed in" boxSize: 20; labelFontSize: 15 color: hifi.colors.black onCheckedChanged: { - Settings.setValue("wallet/autoLogout", !checked); + Settings.setValue("keepMeLoggedIn", checked); if (checked) { - Settings.setValue("wallet/savedUsername", Account.username); + Settings.setValue("keepMeLoggedIn/savedUsername", Account.username); } else { - Settings.setValue("wallet/savedUsername", ""); + Settings.setValue("keepMeLoggedIn/savedUsername", ""); } } Component.onDestruction: { - Settings.setValue("wallet/autoLogout", !checked); + Settings.setValue("keepMeLoggedIn", checked); } } diff --git a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml index 9d9216c461..5ddfa98923 100644 --- a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml +++ b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml @@ -14,9 +14,9 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit -import "../../../controls" as HifiControls +import "qrc:////qml//styles-uit" +import "qrc:////qml//controls-uit" as HifiControlsUit +import "qrc:////qml//controls" as HifiControls // references XXX from root context @@ -40,6 +40,8 @@ Rectangle { anchors.fill: parent; color: Qt.rgba(0, 0, 0, 0.5); z: 999; + + HifiConstants { id: hifi; } onVisibleChanged: { if (!visible) { diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml deleted file mode 100644 index 14ac696ef7..0000000000 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ /dev/null @@ -1,246 +0,0 @@ -// -// Security.qml -// qml/hifi/commerce/wallet -// -// Security -// -// Created by Zach Fox on 2017-08-18 -// Copyright 2017 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 -// - -import Hifi 1.0 as Hifi -import QtQuick 2.5 -import QtGraphicalEffects 1.0 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit -import "../../../controls" as HifiControls - -// references XXX from root context - -Item { - HifiConstants { id: hifi; } - - id: root; - property string keyFilePath; - - Connections { - target: Commerce; - - onKeyFilePathIfExistsResult: { - root.keyFilePath = path; - } - } - - // Username Text - RalewayRegular { - id: usernameText; - text: Account.username; - // Text size - size: 24; - // Style - color: hifi.colors.white; - elide: Text.ElideRight; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 20; - width: parent.width/2; - height: 80; - } - - Item { - id: securityContainer; - anchors.top: usernameText.bottom; - anchors.topMargin: 20; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.bottom: parent.bottom; - - RalewaySemiBold { - id: securityText; - text: "Security"; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 20; - anchors.right: parent.right; - height: 30; - // Text size - size: 18; - // Style - color: hifi.colors.blueHighlight; - } - - Rectangle { - id: securityTextSeparator; - // Size - width: parent.width; - height: 1; - // Anchors - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: securityText.bottom; - anchors.topMargin: 8; - // Style - color: hifi.colors.faintGray; - } - - Item { - id: changeSecurityImageContainer; - anchors.top: securityTextSeparator.bottom; - anchors.topMargin: 8; - anchors.left: parent.left; - anchors.leftMargin: 40; - anchors.right: parent.right; - anchors.rightMargin: 55; - height: 75; - - HiFiGlyphs { - id: changeSecurityImageImage; - text: hifi.glyphs.securityImage; - // Size - size: 80; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - // Style - color: hifi.colors.white; - } - - RalewaySemiBold { - text: "Security Pic"; - // Anchors - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: changeSecurityImageImage.right; - anchors.leftMargin: 30; - width: 50; - // Text size - size: 18; - // Style - color: hifi.colors.white; - } - - // "Change Security Pic" button - HifiControlsUit.Button { - id: changeSecurityImageButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; - anchors.verticalCenter: parent.verticalCenter; - width: 140; - height: 40; - text: "Change"; - onClicked: { - sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'}); - } - } - } - - Item { - id: autoLogoutContainer; - anchors.top: changeSecurityImageContainer.bottom; - anchors.topMargin: 8; - anchors.left: parent.left; - anchors.leftMargin: 40; - anchors.right: parent.right; - anchors.rightMargin: 55; - height: 75; - - HiFiGlyphs { - id: autoLogoutImage; - text: hifi.glyphs.walletKey; - // Size - size: 80; - // Anchors - anchors.top: parent.top; - anchors.topMargin: 20; - anchors.left: parent.left; - // Style - color: hifi.colors.white; - } - - HifiControlsUit.CheckBox { - id: autoLogoutCheckbox; - checked: Settings.getValue("wallet/autoLogout", false); - text: "Automatically Log Out when Exiting Interface" - // Anchors - anchors.verticalCenter: autoLogoutImage.verticalCenter; - anchors.left: autoLogoutImage.right; - anchors.leftMargin: 20; - anchors.right: autoLogoutHelp.left; - anchors.rightMargin: 12; - boxSize: 28; - labelFontSize: 18; - color: hifi.colors.white; - onCheckedChanged: { - Settings.setValue("wallet/autoLogout", checked); - if (checked) { - Settings.setValue("wallet/savedUsername", Account.username); - } else { - Settings.setValue("wallet/savedUsername", ""); - } - } - } - - RalewaySemiBold { - id: autoLogoutHelp; - text: '[?]'; - // Anchors - anchors.verticalCenter: autoLogoutImage.verticalCenter; - anchors.right: parent.right; - width: 30; - height: 30; - // Text size - size: 18; - // Style - color: hifi.colors.blueHighlight; - - MouseArea { - anchors.fill: parent; - hoverEnabled: true; - onEntered: { - parent.color = hifi.colors.blueAccent; - } - onExited: { - parent.color = hifi.colors.blueHighlight; - } - onClicked: { - sendSignalToWallet({method: 'walletSecurity_autoLogoutHelp'}); - } - } - } - } - } - - // - // FUNCTION DEFINITIONS START - // - // - // Function Name: fromScript() - // - // Relevant Variables: - // None - // - // Arguments: - // message: The message sent from the JavaScript. - // Messages are in format "{method, params}", like json-rpc. - // - // Description: - // Called when a message is received from a script. - // - function fromScript(message) { - switch (message.method) { - default: - console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); - } - } - signal sendSignalToWallet(var msg); - // - // FUNCTION DEFINITIONS END - // -} diff --git a/interface/resources/qml/hifi/dialogs/security/Security.qml b/interface/resources/qml/hifi/dialogs/security/Security.qml new file mode 100644 index 0000000000..d1dc2a9e11 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/security/Security.qml @@ -0,0 +1,306 @@ +// +// Security.qml +// qml\hifi\dialogs\security +// +// Security +// +// Created by Zach Fox on 2018-10-31 +// 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 +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit +import "qrc:////qml//controls" as HifiControls +import "qrc:////qml//hifi//commerce//common" as HifiCommerceCommon + +// references XXX from root context + +Rectangle { + HifiStylesUit.HifiConstants { id: hifi; } + + id: root; + color: hifi.colors.darkGray; + + property string title: "Security Settings"; + property bool walletSetUp; + + Connections { + target: Commerce; + + onWalletStatusResult: { + if (walletStatus === 5) { + Commerce.getSecurityImage(); + root.walletSetUp = true; + } else { + root.walletSetUp = false; + } + } + + onSecurityImageResult: { + if (exists) { + currentSecurityPicture.source = ""; + currentSecurityPicture.source = "image://security/securityImage"; + } + } + } + + HifiCommerceCommon.CommerceLightbox { + id: lightboxPopup; + visible: false; + anchors.fill: parent; + } + + // Username Text + HifiStylesUit.RalewayRegular { + id: usernameText; + text: Account.username === "" ? Account.username : "Please Log In"; + // Text size + size: 24; + // Style + color: hifi.colors.white; + elide: Text.ElideRight; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 80; + } + + Item { + id: pleaseLogInContainer; + visible: Account.username === ""; + anchors.top: usernameText.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + + HifiStylesUit.RalewayRegular { + text: "Please log in for security settings." + // Text size + size: 24; + // Style + color: hifi.colors.white; + // Anchors + anchors.bottom: openLoginButton.top; + anchors.left: parent.left; + anchors.right: parent.right; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + height: 80; + } + + HifiControlsUit.Button { + id: openLoginButton; + color: hifi.buttons.white; + colorScheme: hifi.colorSchemes.dark; + anchors.centerIn: parent; + width: 140; + height: 40; + text: "Change"; + onClicked: { + DialogsManager.showLoginDialog(); + } + } + } + + Item { + id: securitySettingsContainer; + visible: !pleaseLogInContainer.visible; + anchors.top: usernameText.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + + Item { + id: accountContainer; + anchors.top: securitySettingsContainer.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + + Rectangle { + id: accountHeaderContainer; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 80; + color: hifi.colors.baseGrayHighlight; + + HifiStylesUit.RalewaySemiBold { + text: "Account"; + anchors.fill: parent; + anchors.leftMargin: 20; + } + } + + Item { + id: keepMeLoggedInContainer; + anchors.top: accountHeaderContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 80; + + HifiControlsUit.CheckBox { + id: autoLogoutCheckbox; + checked: Settings.getValue("keepMeLoggedIn", false); + text: "Keep Me Logged In" + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + anchors.leftMargin: 20; + boxSize: 28; + labelFontSize: 18; + color: hifi.colors.white; + onCheckedChanged: { + Settings.setValue("keepMeLoggedIn", checked); + if (checked) { + Settings.setValue("keepMeLoggedIn/savedUsername", Account.username); + } else { + Settings.setValue("keepMeLoggedIn/savedUsername", ""); + } + } + } + + HifiStylesUit.RalewaySemiBold { + id: autoLogoutHelp; + text: '[?]'; + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.right: autoLogoutCheckbox.right; + width: 30; + height: 30; + // Text size + size: 18; + // Style + color: hifi.colors.blueHighlight; + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.color = hifi.colors.blueAccent; + } + onExited: { + parent.color = hifi.colors.blueHighlight; + } + onClicked: { + lightboxPopup.titleText = "Keep Me Logged In"; + lightboxPopup.bodyText = "If you choose to stay logged in, ensure that this is a trusted device.\n\n" + + "Also, remember that logging out may not disconnect you from a domain."; + lightboxPopup.button1text = "OK"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.visible = true; + } + } + } + } + } + + Item { + id: walletContainer; + anchors.top: accountContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: childrenRect.height; + + Rectangle { + id: walletHeaderContainer; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 80; + color: hifi.colors.baseGrayHighlight; + + HifiStylesUit.RalewaySemiBold { + text: "Wallet"; + anchors.fill: parent; + anchors.leftMargin: 20; + } + } + + Item { + id: walletSecurityPictureContainer; + visible: root.walletSetUp; + anchors.top: walletHeaderContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 80; + + Image { + id: currentSecurityPicture; + source: ""; + visible: true; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.verticalCenter: parent.verticalCenter; + height: 40; + width: height; + mipmap: true; + cache: false; + } + + HifiStylesUit.RalewayRegular { + id: securityPictureText; + text: "Wallet Security Picture"; + // Anchors + anchors.top: parent.top; + anchors.bottom: parent.bottom; + anchors.left: currentSecurityPicture.right; + anchors.leftMargin: 12; + width: paintedWidth; + // Text size + size: 18; + // Style + color: hifi.colors.white; + } + + // "Change Security Pic" button + HifiControlsUit.Button { + id: changeSecurityImageButton; + color: hifi.buttons.white; + colorScheme: hifi.colorSchemes.dark; + anchors.left: securityPictureText.right; + anchors.verticalCenter: parent.verticalCenter; + width: 140; + height: 40; + text: "Change"; + onClicked: { + + } + } + } + + Item { + id: walletNotSetUpContainer; + visible: !root.walletSetUp; + anchors.top: walletHeaderContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 80; + + HifiStylesUit.RalewayRegular { + text: "Your wallet is not set up.\n" + + "Open the WALLET app to get started."; + // Anchors + anchors.fill: parent; + // Text size + size: 18; + // Style + color: hifi.colors.white; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + } + } + } +} diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml b/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml similarity index 89% rename from interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml rename to interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml index 01df18352b..d953a764fd 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml @@ -1,11 +1,11 @@ // // SecurityImageChange.qml -// qml/hifi/commerce/wallet +// qml\hifi\dialogs\security // -// SecurityImageChange +// Security // -// Created by Zach Fox on 2017-08-18 -// Copyright 2017 High Fidelity, Inc. +// Created by Zach Fox on 2018-10-31 +// 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 @@ -13,9 +13,9 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit -import "../../../controls" as HifiControls +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit +import "qrc:////qml//controls" as HifiControls // references XXX from root context @@ -33,7 +33,7 @@ Item { securityImageChangePageSecurityImage.source = "image://security/securityImage"; if (exists) { // Success submitting new security image if (root.justSubmitted) { - sendSignalToWallet({method: "walletSecurity_changeSecurityImageSuccess"}); + sendSignalToParent({method: "walletSecurity_changeSecurityImageSuccess"}); root.justSubmitted = false; } } else if (root.justSubmitted) { @@ -83,10 +83,10 @@ Item { verticalAlignment: Text.AlignBottom; color: hifi.colors.white; } - // "Security image" text below pic + // "Security image" text below image RalewayRegular { id: securityImageText; - text: "SECURITY PIC"; + text: "SECURITY IMAGE"; // Text size size: 12; // Anchors @@ -118,7 +118,7 @@ Item { // "Change Security Image" text RalewaySemiBold { id: securityImageTitle; - text: "Change Security Pic:"; + text: "Change Security Image:"; // Text size size: 18; anchors.top: parent.top; @@ -139,12 +139,6 @@ Item { anchors.right: parent.right; anchors.rightMargin: 16; height: 300; - - Connections { - onSendSignalToWallet: { - sendSignalToWallet(msg); - } - } } // Navigation Bar @@ -169,7 +163,7 @@ Item { width: 150; text: "Cancel" onClicked: { - sendSignalToWallet({method: "walletSecurity_changeSecurityImageCancelled"}); + sendSignalToParent({method: "walletSecurity_changeSecurityImageCancelled"}); } } @@ -197,7 +191,7 @@ Item { // SECURITY IMAGE SELECTION END // - signal sendSignalToWallet(var msg); + signal sendSignalToParent(var msg); function initModel() { securityImageSelection.initModel(); diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml b/interface/resources/qml/hifi/dialogs/security/SecurityImageModel.qml similarity index 89% rename from interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml rename to interface/resources/qml/hifi/dialogs/security/SecurityImageModel.qml index b8e9db09ab..946f979c1a 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageModel.qml +++ b/interface/resources/qml/hifi/dialogs/security/SecurityImageModel.qml @@ -1,11 +1,11 @@ // // SecurityImageModel.qml -// qml/hifi/commerce +// qml\hifi\dialogs\security // -// SecurityImageModel +// Security // -// Created by Zach Fox on 2017-08-17 -// Copyright 2017 High Fidelity, Inc. +// Created by Zach Fox on 2018-10-31 +// 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 diff --git a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml b/interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml similarity index 88% rename from interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml rename to interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml index 599c6a1851..5a05a28ba3 100644 --- a/interface/resources/qml/hifi/commerce/wallet/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml @@ -1,11 +1,11 @@ // // SecurityImageSelection.qml -// qml/hifi/commerce/wallet +// qml\hifi\dialogs\security // -// SecurityImageSelection +// Security // -// Created by Zach Fox on 2017-08-17 -// Copyright 2017 High Fidelity, Inc. +// Created by Zach Fox on 2018-10-31 +// 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 @@ -13,9 +13,9 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 -import "../../../styles-uit" -import "../../../controls-uit" as HifiControlsUit -import "../../../controls" as HifiControls +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit +import "qrc:////qml//controls" as HifiControls // references XXX from root context @@ -73,8 +73,6 @@ Item { // // FUNCTION DEFINITIONS START // - signal sendSignalToWallet(var msg); - function getImagePathFromImageID(imageID) { return (imageID ? gridModel.getImagePathFromImageID(imageID) : ""); } diff --git a/interface/resources/qml/hifi/dialogs/security/SecurityWrapper.qml b/interface/resources/qml/hifi/dialogs/security/SecurityWrapper.qml new file mode 100644 index 0000000000..7c17818225 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/security/SecurityWrapper.qml @@ -0,0 +1,30 @@ +// +// SecurityWrapper.qml +// qml\hifi\dialogs\security +// +// SecurityWrapper +// +// Created by Zach Fox on 2018-10-31 +// 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 +// + +import "qrc:////qml//windows" +import "./" + +ScrollingWindow { + id: root; + + resizable: true; + destroyOnHidden: true; + width: 400; + height: 577; + minSize: Qt.vector2d(400, 500); + + Security { id: security; width: root.width } + + objectName: "SecurityDialog"; + title: security.title; +} diff --git a/interface/resources/qml/hifi/commerce/wallet/images/01.jpg b/interface/resources/qml/hifi/dialogs/security/images/01.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/01.jpg rename to interface/resources/qml/hifi/dialogs/security/images/01.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/02.jpg b/interface/resources/qml/hifi/dialogs/security/images/02.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/02.jpg rename to interface/resources/qml/hifi/dialogs/security/images/02.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/03.jpg b/interface/resources/qml/hifi/dialogs/security/images/03.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/03.jpg rename to interface/resources/qml/hifi/dialogs/security/images/03.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/04.jpg b/interface/resources/qml/hifi/dialogs/security/images/04.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/04.jpg rename to interface/resources/qml/hifi/dialogs/security/images/04.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/05.jpg b/interface/resources/qml/hifi/dialogs/security/images/05.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/05.jpg rename to interface/resources/qml/hifi/dialogs/security/images/05.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/06.jpg b/interface/resources/qml/hifi/dialogs/security/images/06.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/06.jpg rename to interface/resources/qml/hifi/dialogs/security/images/06.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/07.jpg b/interface/resources/qml/hifi/dialogs/security/images/07.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/07.jpg rename to interface/resources/qml/hifi/dialogs/security/images/07.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/08.jpg b/interface/resources/qml/hifi/dialogs/security/images/08.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/08.jpg rename to interface/resources/qml/hifi/dialogs/security/images/08.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/09.jpg b/interface/resources/qml/hifi/dialogs/security/images/09.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/09.jpg rename to interface/resources/qml/hifi/dialogs/security/images/09.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/10.jpg b/interface/resources/qml/hifi/dialogs/security/images/10.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/10.jpg rename to interface/resources/qml/hifi/dialogs/security/images/10.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/11.jpg b/interface/resources/qml/hifi/dialogs/security/images/11.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/11.jpg rename to interface/resources/qml/hifi/dialogs/security/images/11.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/12.jpg b/interface/resources/qml/hifi/dialogs/security/images/12.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/12.jpg rename to interface/resources/qml/hifi/dialogs/security/images/12.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/13.jpg b/interface/resources/qml/hifi/dialogs/security/images/13.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/13.jpg rename to interface/resources/qml/hifi/dialogs/security/images/13.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/14.jpg b/interface/resources/qml/hifi/dialogs/security/images/14.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/14.jpg rename to interface/resources/qml/hifi/dialogs/security/images/14.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/15.jpg b/interface/resources/qml/hifi/dialogs/security/images/15.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/15.jpg rename to interface/resources/qml/hifi/dialogs/security/images/15.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/16.jpg b/interface/resources/qml/hifi/dialogs/security/images/16.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/16.jpg rename to interface/resources/qml/hifi/dialogs/security/images/16.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/17.jpg b/interface/resources/qml/hifi/dialogs/security/images/17.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/17.jpg rename to interface/resources/qml/hifi/dialogs/security/images/17.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/18.jpg b/interface/resources/qml/hifi/dialogs/security/images/18.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/18.jpg rename to interface/resources/qml/hifi/dialogs/security/images/18.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/19.jpg b/interface/resources/qml/hifi/dialogs/security/images/19.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/19.jpg rename to interface/resources/qml/hifi/dialogs/security/images/19.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/20.jpg b/interface/resources/qml/hifi/dialogs/security/images/20.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/20.jpg rename to interface/resources/qml/hifi/dialogs/security/images/20.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/21.jpg b/interface/resources/qml/hifi/dialogs/security/images/21.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/21.jpg rename to interface/resources/qml/hifi/dialogs/security/images/21.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/22.jpg b/interface/resources/qml/hifi/dialogs/security/images/22.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/22.jpg rename to interface/resources/qml/hifi/dialogs/security/images/22.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/23.jpg b/interface/resources/qml/hifi/dialogs/security/images/23.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/23.jpg rename to interface/resources/qml/hifi/dialogs/security/images/23.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/24.jpg b/interface/resources/qml/hifi/dialogs/security/images/24.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/24.jpg rename to interface/resources/qml/hifi/dialogs/security/images/24.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/25.jpg b/interface/resources/qml/hifi/dialogs/security/images/25.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/25.jpg rename to interface/resources/qml/hifi/dialogs/security/images/25.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/26.jpg b/interface/resources/qml/hifi/dialogs/security/images/26.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/26.jpg rename to interface/resources/qml/hifi/dialogs/security/images/26.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/27.jpg b/interface/resources/qml/hifi/dialogs/security/images/27.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/27.jpg rename to interface/resources/qml/hifi/dialogs/security/images/27.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/28.jpg b/interface/resources/qml/hifi/dialogs/security/images/28.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/28.jpg rename to interface/resources/qml/hifi/dialogs/security/images/28.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/29.jpg b/interface/resources/qml/hifi/dialogs/security/images/29.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/29.jpg rename to interface/resources/qml/hifi/dialogs/security/images/29.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/30.jpg b/interface/resources/qml/hifi/dialogs/security/images/30.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/30.jpg rename to interface/resources/qml/hifi/dialogs/security/images/30.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/31.jpg b/interface/resources/qml/hifi/dialogs/security/images/31.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/31.jpg rename to interface/resources/qml/hifi/dialogs/security/images/31.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/32.jpg b/interface/resources/qml/hifi/dialogs/security/images/32.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/32.jpg rename to interface/resources/qml/hifi/dialogs/security/images/32.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/33.jpg b/interface/resources/qml/hifi/dialogs/security/images/33.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/33.jpg rename to interface/resources/qml/hifi/dialogs/security/images/33.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/34.jpg b/interface/resources/qml/hifi/dialogs/security/images/34.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/34.jpg rename to interface/resources/qml/hifi/dialogs/security/images/34.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/lowerKeyboard.png b/interface/resources/qml/hifi/dialogs/security/images/lowerKeyboard.png similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/lowerKeyboard.png rename to interface/resources/qml/hifi/dialogs/security/images/lowerKeyboard.png diff --git a/interface/resources/qml/hifi/commerce/wallet/images/wallet-bg.jpg b/interface/resources/qml/hifi/dialogs/security/images/wallet-bg.jpg similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/wallet-bg.jpg rename to interface/resources/qml/hifi/dialogs/security/images/wallet-bg.jpg diff --git a/interface/resources/qml/hifi/commerce/wallet/images/wallet-tip-bg.png b/interface/resources/qml/hifi/dialogs/security/images/wallet-tip-bg.png similarity index 100% rename from interface/resources/qml/hifi/commerce/wallet/images/wallet-tip-bg.png rename to interface/resources/qml/hifi/dialogs/security/images/wallet-tip-bg.png diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7cd91d76a0..1ca71a70d9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -382,7 +382,7 @@ static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop"; static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin"; static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; -static const QString AUTO_LOGOUT_SETTING_NAME = "wallet/autoLogout"; +static const QString KEEP_ME_LOGGED_IN_SETTING_NAME = "keepMeLoggedIn"; const std::vector> Application::_acceptedExtensions { { SVO_EXTENSION, &Application::importSVOFromURL }, @@ -2567,7 +2567,7 @@ void Application::cleanupBeforeQuit() { } DependencyManager::destroy(); - bool autoLogout = Setting::Handle(AUTO_LOGOUT_SETTING_NAME, false).get(); + bool autoLogout = Setting::Handle(KEEP_ME_LOGGED_IN_SETTING_NAME, false).get(); if (autoLogout) { DependencyManager::get()->removeAccountFromFile(); } @@ -2944,13 +2944,13 @@ void Application::initializeUi() { QUrl{ "hifi/commerce/wallet/PassphraseChange.qml" }, QUrl{ "hifi/commerce/wallet/PassphraseModal.qml" }, QUrl{ "hifi/commerce/wallet/PassphraseSelection.qml" }, - QUrl{ "hifi/commerce/wallet/Security.qml" }, - QUrl{ "hifi/commerce/wallet/SecurityImageChange.qml" }, - QUrl{ "hifi/commerce/wallet/SecurityImageModel.qml" }, - QUrl{ "hifi/commerce/wallet/SecurityImageSelection.qml" }, QUrl{ "hifi/commerce/wallet/Wallet.qml" }, QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, QUrl{ "hifi/commerce/wallet/WalletSetup.qml" }, + QUrl{ "hifi/dialogs/security/Security.qml" }, + QUrl{ "hifi/dialogs/security/SecurityImageChange.qml" }, + QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" }, + QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" }, }, callback); qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 539bdabe7d..e16da6781d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -265,6 +265,13 @@ Menu::Menu() { QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog"); }); + // Settings > Security... + action = addActionToQMenuAndActionHash(settingsMenu, "Security..."); + connect(action, &QAction::triggered, [] { + qApp->showDialog(QString("hifi/dialogs/security/SecurityWrapper.qml"), + QString("hifi/dialogs/security/Security.qml"), "SecurityDialog"); + }); + // Settings > Developer Menu addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus())); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 5b91afea33..2d44650d9c 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -376,9 +376,9 @@ function deleteSendMoneyParticleEffect() { } function onUsernameChanged() { - if (Account.username !== Settings.getValue("wallet/savedUsername")) { - Settings.setValue("wallet/autoLogout", false); - Settings.setValue("wallet/savedUsername", ""); + if (Account.username !== Settings.getValue("keepMeLoggedIn/savedUsername")) { + Settings.setValue("keepMeLoggedIn", false); + Settings.setValue("keepMeLoggedIn/savedUsername", ""); } } From e6c3e7e67f31b30613bf1a86dd60ece38731bfb7 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 31 Oct 2018 14:41:49 -0700 Subject: [PATCH 270/276] initial purge of security tab from wallet --- .../qml/hifi/commerce/wallet/Help.qml | 7 +- .../qml/hifi/commerce/wallet/Wallet.qml | 132 +----------------- 2 files changed, 8 insertions(+), 131 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 6d8fc3c33f..6cf00f78eb 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -74,8 +74,7 @@ In your Wallet's Send Money tab, choose from your list of connections, or choose isExpanded: false; question: "What is a Security Pic?" answer: "Your Security Pic acts as an extra layer of Wallet security. \ -When you see your Security Pic, you know that your actions and data are securely making use of your account. \ -

Tap here to change your Security Pic."; +When you see your Security Pic, you know that your actions and data are securely making use of your account."; } ListElement { isExpanded: false; @@ -137,7 +136,7 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta anchors.left: parent.left; width: parent.width; height: questionText.paintedHeight + 50; - + RalewaySemiBold { id: plusMinusButton; text: model.isExpanded ? "-" : "+"; @@ -217,8 +216,6 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta } } else if (link === "#support") { Qt.openUrlExternally("mailto:support@highfidelity.com"); - } else if (link === "#securitypic") { - sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'}); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index cbb77883df..f2119a1a8f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -232,10 +232,6 @@ Rectangle { root.isPassword = msg.isPasswordField; } else if (msg.method === 'walletSetup_lowerKeyboard') { root.keyboardRaised = false; - } else if (msg.method === 'walletSecurity_changePassphraseCancelled') { - root.activeView = "security"; - } else if (msg.method === 'walletSecurity_changePassphraseSuccess') { - root.activeView = "security"; } else { sendToScript(msg); } @@ -245,27 +241,6 @@ Rectangle { } } } - SecurityImageChange { - id: securityImageChange; - visible: root.activeView === "securityImageChange"; - z: 997; - anchors.top: titleBarContainer.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.bottom: parent.bottom; - - Connections { - onSendSignalToWallet: { - if (msg.method === 'walletSecurity_changeSecurityImageCancelled') { - root.activeView = "security"; - } else if (msg.method === 'walletSecurity_changeSecurityImageSuccess') { - root.activeView = "security"; - } else { - sendToScript(msg); - } - } - } - } // // TAB CONTENTS START @@ -366,39 +341,6 @@ Rectangle { } } - Security { - id: security; - visible: root.activeView === "security"; - anchors.top: titleBarContainer.bottom; - anchors.bottom: tabButtonsContainer.top; - anchors.left: parent.left; - anchors.right: parent.right; - - Connections { - onSendSignalToWallet: { - if (msg.method === 'walletSecurity_changePassphrase') { - root.activeView = "passphraseChange"; - passphraseChange.clearPassphraseFields(); - passphraseChange.resetSubmitButton(); - } else if (msg.method === 'walletSecurity_changeSecurityImage') { - securityImageChange.initModel(); - root.activeView = "securityImageChange"; - } else if (msg.method === 'walletSecurity_autoLogoutHelp') { - lightboxPopup.titleText = "Automatically Log Out"; - lightboxPopup.bodyText = "By default, after you log in to High Fidelity, you will stay logged in to your High Fidelity " + - "account even after you close and re-open Interface. This means anyone who opens Interface on your computer " + - "could make purchases with your Wallet.\n\n" + - "If you do not want to stay logged in across Interface sessions, check this box."; - lightboxPopup.button1text = "CLOSE"; - lightboxPopup.button1method = function() { - lightboxPopup.visible = false; - } - lightboxPopup.visible = true; - } - } - } - } - Help { id: help; visible: root.activeView === "help"; @@ -407,14 +349,6 @@ Rectangle { anchors.left: parent.left; anchors.right: parent.right; - Connections { - onSendSignalToWallet: { - if (msg.method === 'walletSecurity_changeSecurityImage') { - securityImageChange.initModel(); - root.activeView = "securityImageChange"; - } - } - } } @@ -428,7 +362,7 @@ Rectangle { Item { id: tabButtonsContainer; visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange" && sendMoney.currentActiveView !== "sendAssetStep"; - property int numTabs: 5; + property int numTabs: 4; // Size width: root.width; height: 90; @@ -452,7 +386,7 @@ Rectangle { anchors.left: parent.left; anchors.bottom: parent.bottom; width: parent.width / tabButtonsContainer.numTabs; - + HiFiGlyphs { id: homeTabIcon; text: hifi.glyphs.home2; @@ -506,7 +440,7 @@ Rectangle { anchors.left: walletHomeButtonContainer.right; anchors.bottom: parent.bottom; width: parent.width / tabButtonsContainer.numTabs; - + HiFiGlyphs { id: exchangeMoneyTabIcon; text: hifi.glyphs.leftRightArrows; @@ -550,7 +484,7 @@ Rectangle { anchors.left: exchangeMoneyButtonContainer.right; anchors.bottom: parent.bottom; width: parent.width / tabButtonsContainer.numTabs; - + HiFiGlyphs { id: sendMoneyTabIcon; text: hifi.glyphs.paperPlane; @@ -596,70 +530,16 @@ Rectangle { } } - // "SECURITY" tab button - Rectangle { - id: securityButtonContainer; - visible: !walletSetup.visible; - color: root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black; - anchors.top: parent.top; - anchors.left: sendMoneyButtonContainer.right; - anchors.bottom: parent.bottom; - width: parent.width / tabButtonsContainer.numTabs; - - HiFiGlyphs { - id: securityTabIcon; - text: hifi.glyphs.lock; - // Size - size: 38; - // Anchors - anchors.horizontalCenter: parent.horizontalCenter; - anchors.top: parent.top; - anchors.topMargin: 2; - // Style - color: root.activeView === "security" || securityTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; - } - - RalewaySemiBold { - text: "SECURITY"; - // Text size - size: 16; - // Anchors - anchors.bottom: parent.bottom; - height: parent.height/2; - anchors.left: parent.left; - anchors.leftMargin: 4; - anchors.right: parent.right; - anchors.rightMargin: 4; - // Style - color: root.activeView === "security" || securityTabMouseArea.containsMouse ? hifi.colors.white : hifi.colors.blueHighlight; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignTop; - } - MouseArea { - id: securityTabMouseArea; - anchors.fill: parent; - hoverEnabled: enabled; - onClicked: { - root.activeView = "security"; - tabButtonsContainer.resetTabButtonColors(); - } - onEntered: parent.color = hifi.colors.blueHighlight; - onExited: parent.color = root.activeView === "security" ? hifi.colors.blueAccent : hifi.colors.black; - } - } - // "HELP" tab button Rectangle { id: helpButtonContainer; visible: !walletSetup.visible; color: root.activeView === "help" ? hifi.colors.blueAccent : hifi.colors.black; anchors.top: parent.top; - anchors.left: securityButtonContainer.right; + anchors.left: sendMoneyButtonContainer.right; anchors.bottom: parent.bottom; width: parent.width / tabButtonsContainer.numTabs; - + HiFiGlyphs { id: helpTabIcon; text: hifi.glyphs.question; From d4e9a7056409e7e071af495917d8be91312c5c19 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 31 Oct 2018 16:12:16 -0700 Subject: [PATCH 271/276] Almost there --- .../qml/LoginDialog/LinkAccountBody.qml | 15 ++- .../qml/hifi/commerce/wallet/Wallet.qml | 4 +- .../qml/hifi/commerce/wallet/WalletSetup.qml | 120 ------------------ .../qml/hifi/dialogs/security/Security.qml | 62 +++++++-- .../dialogs/security/SecurityImageChange.qml | 13 +- .../security/SecurityImageSelection.qml | 10 +- .../hifi/dialogs/security/SecurityWrapper.qml | 30 ----- interface/src/Application.cpp | 4 +- interface/src/Menu.cpp | 5 +- interface/src/commerce/Wallet.cpp | 2 +- 10 files changed, 82 insertions(+), 183 deletions(-) delete mode 100644 interface/resources/qml/hifi/dialogs/security/SecurityWrapper.qml diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index d5d89cd0b4..3c0577532a 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -239,7 +239,10 @@ Item { } - Keys.onReturnPressed: linkAccountBody.login() + Keys.onReturnPressed: { + Settings.setValue("keepMeLoggedIn/savedUsername", usernameField.text); + linkAccountBody.login(); + } } InfoItem { @@ -264,14 +267,14 @@ Item { CheckBox { id: autoLogoutCheckbox checked: Settings.getValue("keepMeLoggedIn", false) - text: "Keep me signed in" + text: "Keep me logged in" boxSize: 20; labelFontSize: 15 color: hifi.colors.black onCheckedChanged: { Settings.setValue("keepMeLoggedIn", checked); if (checked) { - Settings.setValue("keepMeLoggedIn/savedUsername", Account.username); + Settings.setValue("keepMeLoggedIn/savedUsername", usernameField.text); } else { Settings.setValue("keepMeLoggedIn/savedUsername", ""); } @@ -289,7 +292,10 @@ Item { text: qsTr(loginDialog.isSteamRunning() ? "Link Account" : "Log in") color: hifi.buttons.blue - onClicked: linkAccountBody.login() + onClicked: { + Settings.setValue("keepMeLoggedIn/savedUsername", usernameField.text); + linkAccountBody.login(); + } } } @@ -403,6 +409,7 @@ Item { case Qt.Key_Enter: case Qt.Key_Return: event.accepted = true + Settings.setValue("keepMeLoggedIn/savedUsername", usernameField.text); linkAccountBody.login() break } diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index f2119a1a8f..d032f060e2 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -142,7 +142,7 @@ Rectangle { Image { id: titleBarSecurityImage; source: ""; - visible: titleBarSecurityImage.source !== "" && !securityImageChange.visible; + visible: titleBarSecurityImage.source !== ""; anchors.right: parent.right; anchors.rightMargin: 6; anchors.top: parent.top; @@ -361,7 +361,7 @@ Rectangle { // Item { id: tabButtonsContainer; - visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && root.activeView !== "securityImageChange" && sendMoney.currentActiveView !== "sendAssetStep"; + visible: !needsLogIn.visible && root.activeView !== "passphraseChange" && sendMoney.currentActiveView !== "sendAssetStep"; property int numTabs: 4; // Size width: root.width; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index dc6ce45a74..ecd7234400 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -243,7 +243,6 @@ Item { height: 50; text: "Set Up Wallet"; onClicked: { - securityImageSelection.initModel(); root.activeView = "step_2"; } } @@ -267,124 +266,6 @@ Item { // FIRST PAGE END // - // - // SECURITY IMAGE SELECTION START - // - Item { - id: securityImageContainer; - visible: root.activeView === "step_2"; - // Anchors - anchors.top: titleBarContainer.bottom; - anchors.topMargin: 30; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - anchors.leftMargin: 16; - anchors.right: parent.right; - anchors.rightMargin: 16; - - // Text below title bar - RalewayRegular { - id: securityImageTitleHelper; - text: "Choose a Security Pic:"; - // Text size - size: 24; - // Anchors - anchors.top: parent.top; - anchors.left: parent.left; - height: 50; - width: paintedWidth; - // Style - color: hifi.colors.white; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - SecurityImageSelection { - id: securityImageSelection; - // Anchors - anchors.top: securityImageTitleHelper.bottom; - anchors.left: parent.left; - anchors.right: parent.right; - height: 300; - - Connections { - onSendSignalToWallet: { - sendSignalToWallet(msg); - } - } - } - - // Text below security images - RalewayRegular { - text: "Your security picture shows you that the service asking for your passphrase is authorized. You can change your secure picture at any time."; - // Text size - size: 18; - // Anchors - anchors.top: securityImageSelection.bottom; - anchors.topMargin: 40; - anchors.left: parent.left; - anchors.right: parent.right; - height: paintedHeight; - // Style - color: hifi.colors.white; - wrapMode: Text.WordWrap; - // Alignment - horizontalAlignment: Text.AlignHLeft; - verticalAlignment: Text.AlignVCenter; - } - - // Navigation Bar - Item { - // Size - width: parent.width; - height: 50; - // Anchors: - anchors.left: parent.left; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 50; - - // "Back" button - HifiControlsUit.Button { - color: hifi.buttons.noneBorderlessWhite; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.left: parent.left; - anchors.leftMargin: 20; - width: 200; - text: "Back" - onClicked: { - securityImageSelection.resetSelection(); - root.activeView = "step_1"; - } - } - - // "Next" button - HifiControlsUit.Button { - enabled: securityImageSelection.currentIndex !== -1; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - anchors.right: parent.right; - anchors.rightMargin: 20; - width: 200; - text: "Next"; - onClicked: { - root.lastPage = "step_2"; - var securityImagePath = securityImageSelection.getImagePathFromImageID(securityImageSelection.getSelectedImageIndex()) - Commerce.chooseSecurityImage(securityImagePath); - root.activeView = "step_3"; - passphraseSelection.clearPassphraseFields(); - } - } - } - } - // - // SECURITY IMAGE SELECTION END - // - // // SECURE PASSPHRASE SELECTION START // @@ -525,7 +406,6 @@ Item { width: 200; text: "Back" onClicked: { - securityImageSelection.resetSelection(); root.lastPage = "step_3"; root.activeView = "step_2"; } diff --git a/interface/resources/qml/hifi/dialogs/security/Security.qml b/interface/resources/qml/hifi/dialogs/security/Security.qml index d1dc2a9e11..3f4d17e838 100644 --- a/interface/resources/qml/hifi/dialogs/security/Security.qml +++ b/interface/resources/qml/hifi/dialogs/security/Security.qml @@ -29,6 +29,17 @@ Rectangle { property string title: "Security Settings"; property bool walletSetUp; + + QtObject { + id: margins + property real paddings: root.width / 20.25 + + property real sizeCheckBox: root.width / 13.5 + property real sizeText: root.width / 2.5 + property real sizeLevel: root.width / 5.8 + property real sizeDesktop: root.width / 5.8 + property real sizeVR: root.width / 13.5 + } Connections { target: Commerce; @@ -50,16 +61,37 @@ Rectangle { } } + Component.onCompleted: { + Commerce.getWalletStatus(); + } + HifiCommerceCommon.CommerceLightbox { + z: 996; id: lightboxPopup; visible: false; anchors.fill: parent; } + SecurityImageChange { + id: securityImageChange; + visible: false; + z: 997; + anchors.top: usernameText.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + + Connections { + onSendSignalToParent: { + securityImageChange.visible = false; + } + } + } + // Username Text HifiStylesUit.RalewayRegular { id: usernameText; - text: Account.username === "" ? Account.username : "Please Log In"; + text: Account.username === "Unknown user" ? "Please Log In" : Account.username; // Text size size: 24; // Style @@ -71,12 +103,12 @@ Rectangle { anchors.leftMargin: 20; anchors.right: parent.right; anchors.rightMargin: 20; - height: 80; + height: 60; } Item { id: pleaseLogInContainer; - visible: Account.username === ""; + visible: Account.username === "Unknown user"; anchors.top: usernameText.bottom; anchors.left: parent.left; anchors.right: parent.right; @@ -94,7 +126,7 @@ Rectangle { anchors.right: parent.right; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; - height: 80; + height: 60; } HifiControlsUit.Button { @@ -104,7 +136,7 @@ Rectangle { anchors.centerIn: parent; width: 140; height: 40; - text: "Change"; + text: "Log In"; onClicked: { DialogsManager.showLoginDialog(); } @@ -131,13 +163,15 @@ Rectangle { anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right; - height: 80; + height: 70; color: hifi.colors.baseGrayHighlight; HifiStylesUit.RalewaySemiBold { text: "Account"; anchors.fill: parent; anchors.leftMargin: 20; + color: hifi.colors.white; + size: 18; } } @@ -156,9 +190,11 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter; anchors.left: parent.left; anchors.leftMargin: 20; - boxSize: 28; + boxSize: 24; labelFontSize: 18; + colorScheme: hifi.colorSchemes.dark color: hifi.colors.white; + width: 240; onCheckedChanged: { Settings.setValue("keepMeLoggedIn", checked); if (checked) { @@ -218,13 +254,15 @@ Rectangle { anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right; - height: 80; + height: 70; color: hifi.colors.baseGrayHighlight; HifiStylesUit.RalewaySemiBold { text: "Wallet"; anchors.fill: parent; anchors.leftMargin: 20; + color: hifi.colors.white; + size: 18; } } @@ -249,7 +287,7 @@ Rectangle { cache: false; } - HifiStylesUit.RalewayRegular { + HifiStylesUit.RalewaySemiBold { id: securityPictureText; text: "Wallet Security Picture"; // Anchors @@ -270,12 +308,14 @@ Rectangle { color: hifi.buttons.white; colorScheme: hifi.colorSchemes.dark; anchors.left: securityPictureText.right; + anchors.leftMargin: 12; anchors.verticalCenter: parent.verticalCenter; width: 140; height: 40; text: "Change"; onClicked: { - + securityImageChange.visible = true; + securityImageChange.initModel(); } } } @@ -286,7 +326,7 @@ Rectangle { anchors.top: walletHeaderContainer.bottom; anchors.left: parent.left; anchors.right: parent.right; - height: 80; + height: 60; HifiStylesUit.RalewayRegular { text: "Your wallet is not set up.\n" + diff --git a/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml b/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml index d953a764fd..0007b98291 100644 --- a/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml @@ -19,10 +19,11 @@ import "qrc:////qml//controls" as HifiControls // references XXX from root context -Item { - HifiConstants { id: hifi; } +Rectangle { + HifiStylesUit.HifiConstants { id: hifi; } id: root; + color: hifi.colors.darkGray; property bool justSubmitted: false; Connections { @@ -72,7 +73,7 @@ Item { anchors.bottom: parent.bottom; height: 22; // Lock icon - HiFiGlyphs { + HifiStylesUit.HiFiGlyphs { id: lockIcon; text: hifi.glyphs.lock; anchors.bottom: parent.bottom; @@ -84,9 +85,9 @@ Item { color: hifi.colors.white; } // "Security image" text below image - RalewayRegular { + HifiStylesUit.RalewayRegular { id: securityImageText; - text: "SECURITY IMAGE"; + text: "SECURITY PIC"; // Text size size: 12; // Anchors @@ -116,7 +117,7 @@ Item { anchors.bottom: parent.bottom; // "Change Security Image" text - RalewaySemiBold { + HifiStylesUit.RalewaySemiBold { id: securityImageTitle; text: "Change Security Image:"; // Text size diff --git a/interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml b/interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml index 5a05a28ba3..f058aad40a 100644 --- a/interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml +++ b/interface/resources/qml/hifi/dialogs/security/SecurityImageSelection.qml @@ -20,7 +20,7 @@ import "qrc:////qml//controls" as HifiControls // references XXX from root context Item { - HifiConstants { id: hifi; } + HifiStylesUit.HifiConstants { id: hifi; } id: root; property alias currentIndex: securityImageGrid.currentIndex; @@ -64,10 +64,10 @@ Item { } } highlight: Rectangle { - width: securityImageGrid.cellWidth; - height: securityImageGrid.cellHeight; - color: hifi.colors.blueHighlight; - } + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + color: hifi.colors.blueHighlight; + } } // diff --git a/interface/resources/qml/hifi/dialogs/security/SecurityWrapper.qml b/interface/resources/qml/hifi/dialogs/security/SecurityWrapper.qml deleted file mode 100644 index 7c17818225..0000000000 --- a/interface/resources/qml/hifi/dialogs/security/SecurityWrapper.qml +++ /dev/null @@ -1,30 +0,0 @@ -// -// SecurityWrapper.qml -// qml\hifi\dialogs\security -// -// SecurityWrapper -// -// Created by Zach Fox on 2018-10-31 -// 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 -// - -import "qrc:////qml//windows" -import "./" - -ScrollingWindow { - id: root; - - resizable: true; - destroyOnHidden: true; - width: 400; - height: 577; - minSize: Qt.vector2d(400, 500); - - Security { id: security; width: root.width } - - objectName: "SecurityDialog"; - title: security.title; -} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1ca71a70d9..245e6c0017 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2567,8 +2567,8 @@ void Application::cleanupBeforeQuit() { } DependencyManager::destroy(); - bool autoLogout = Setting::Handle(KEEP_ME_LOGGED_IN_SETTING_NAME, false).get(); - if (autoLogout) { + bool keepMeLoggedIn = Setting::Handle(KEEP_ME_LOGGED_IN_SETTING_NAME, false).get(); + if (!keepMeLoggedIn) { DependencyManager::get()->removeAccountFromFile(); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index e16da6781d..3636a2f45a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -268,8 +268,9 @@ Menu::Menu() { // Settings > Security... action = addActionToQMenuAndActionHash(settingsMenu, "Security..."); connect(action, &QAction::triggered, [] { - qApp->showDialog(QString("hifi/dialogs/security/SecurityWrapper.qml"), - QString("hifi/dialogs/security/Security.qml"), "SecurityDialog"); + auto tablet = dynamic_cast( + DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); + tablet->loadQMLSource(QString("hifi/dialogs/security/Security.qml")); }); // Settings > Developer Menu diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 5b8417be7c..0e9ad7d79a 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -687,7 +687,7 @@ void Wallet::chooseSecurityImage(const QString& filename) { delete _securityImage; } QString path = PathUtils::resourcesPath(); - path.append("/qml/hifi/commerce/wallet/"); + path.append("/qml/hifi/dialogs/security/"); path.append(filename); // now create a new security image pixmap From 162a08b9935986a46caaf86648191186e1637d95 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 31 Oct 2018 16:14:26 -0700 Subject: [PATCH 272/276] Restore certain images --- .../wallet}/images/lowerKeyboard.png | Bin .../wallet}/images/wallet-bg.jpg | Bin .../wallet}/images/wallet-tip-bg.png | Bin 3 files changed, 0 insertions(+), 0 deletions(-) rename interface/resources/qml/hifi/{dialogs/security => commerce/wallet}/images/lowerKeyboard.png (100%) rename interface/resources/qml/hifi/{dialogs/security => commerce/wallet}/images/wallet-bg.jpg (100%) rename interface/resources/qml/hifi/{dialogs/security => commerce/wallet}/images/wallet-tip-bg.png (100%) diff --git a/interface/resources/qml/hifi/dialogs/security/images/lowerKeyboard.png b/interface/resources/qml/hifi/commerce/wallet/images/lowerKeyboard.png similarity index 100% rename from interface/resources/qml/hifi/dialogs/security/images/lowerKeyboard.png rename to interface/resources/qml/hifi/commerce/wallet/images/lowerKeyboard.png diff --git a/interface/resources/qml/hifi/dialogs/security/images/wallet-bg.jpg b/interface/resources/qml/hifi/commerce/wallet/images/wallet-bg.jpg similarity index 100% rename from interface/resources/qml/hifi/dialogs/security/images/wallet-bg.jpg rename to interface/resources/qml/hifi/commerce/wallet/images/wallet-bg.jpg diff --git a/interface/resources/qml/hifi/dialogs/security/images/wallet-tip-bg.png b/interface/resources/qml/hifi/commerce/wallet/images/wallet-tip-bg.png similarity index 100% rename from interface/resources/qml/hifi/dialogs/security/images/wallet-tip-bg.png rename to interface/resources/qml/hifi/commerce/wallet/images/wallet-tip-bg.png From d6477993a160e7f8c667849a645c523260c0173b Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 1 Nov 2018 13:08:00 -0700 Subject: [PATCH 273/276] Make it work better with the tablet --- interface/src/Menu.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3636a2f45a..16e8af5683 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -268,9 +268,13 @@ Menu::Menu() { // Settings > Security... action = addActionToQMenuAndActionHash(settingsMenu, "Security..."); connect(action, &QAction::triggered, [] { - auto tablet = dynamic_cast( - DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")); - tablet->loadQMLSource(QString("hifi/dialogs/security/Security.qml")); + auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + auto hmd = DependencyManager::get(); + tablet->pushOntoStack("hifi/dialogs/security/Security.qml"); + + if (!hmd->getShouldShowTablet()) { + hmd->toggleShouldShowTablet(); + } }); // Settings > Developer Menu From 0a15e94fe4de1088806af17489859066c7856171 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 1 Nov 2018 13:27:31 -0700 Subject: [PATCH 274/276] New colors --- .../resources/qml/hifi/dialogs/security/Security.qml | 8 +++----- .../qml/hifi/dialogs/security/SecurityImageChange.qml | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/security/Security.qml b/interface/resources/qml/hifi/dialogs/security/Security.qml index 3f4d17e838..8baff0ac13 100644 --- a/interface/resources/qml/hifi/dialogs/security/Security.qml +++ b/interface/resources/qml/hifi/dialogs/security/Security.qml @@ -19,13 +19,11 @@ import "qrc:////qml//controls-uit" as HifiControlsUit import "qrc:////qml//controls" as HifiControls import "qrc:////qml//hifi//commerce//common" as HifiCommerceCommon -// references XXX from root context - Rectangle { HifiStylesUit.HifiConstants { id: hifi; } id: root; - color: hifi.colors.darkGray; + color: hifi.colors.baseGray; property string title: "Security Settings"; property bool walletSetUp; @@ -163,7 +161,7 @@ Rectangle { anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right; - height: 70; + height: 55; color: hifi.colors.baseGrayHighlight; HifiStylesUit.RalewaySemiBold { @@ -254,7 +252,7 @@ Rectangle { anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right; - height: 70; + height: 55; color: hifi.colors.baseGrayHighlight; HifiStylesUit.RalewaySemiBold { diff --git a/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml b/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml index 0007b98291..96d75e340b 100644 --- a/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml +++ b/interface/resources/qml/hifi/dialogs/security/SecurityImageChange.qml @@ -23,7 +23,7 @@ Rectangle { HifiStylesUit.HifiConstants { id: hifi; } id: root; - color: hifi.colors.darkGray; + color: hifi.colors.baseGray; property bool justSubmitted: false; Connections { From 1f8dde67568f80e70cc888200535f2442cc1792c Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 1 Nov 2018 15:37:16 -0700 Subject: [PATCH 275/276] Transaction string was assuming items given to the marketplace/highfidelity were Gifts and was reporting them as such. Items given to the marketplace are in exchange for updates, and the memo indicates such. --- interface/src/commerce/Ledger.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 67303f2a9b..60cd96c0ca 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -219,7 +219,11 @@ QString transactionString(const QJsonObject& valueObject) { if (!message.isEmpty()) { result += QString("
with memo: \"%1\"").arg(message); } - } else if (sentMoney <= 0 && receivedMoney <= 0 && (sentCerts > 0 || receivedCerts > 0) && !KNOWN_USERS.contains(valueObject["sender_name"].toString())) { + } else if (sentMoney <= 0 && receivedMoney <= 0 && + (sentCerts > 0 || receivedCerts > 0) && + !KNOWN_USERS.contains(valueObject["sender_name"].toString()) && + !KNOWN_USERS.contains(valueObject["recipient_name"].toString()) + ) { // this is a non-HFC asset transfer. if (sentCerts > 0) { QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString()); From 917fc4ad58c5fa368f2d429f86789f7445973827 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 1 Nov 2018 16:50:37 -0700 Subject: [PATCH 276/276] Update language --- .../resources/qml/hifi/commerce/common/CommerceLightbox.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml index 5ddfa98923..5901adc484 100644 --- a/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml +++ b/interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml @@ -33,7 +33,7 @@ Rectangle { property string buttonLayout: "leftright"; readonly property string securityPicBodyText: "When you see your Security Pic, your actions and data are securely making use of your " + - "Wallet's private keys.

You can change your Security Pic in your Wallet."; + "private keys.

You can change your Security Pic via Settings > Security..."; id: root; visible: false;