From 20d7e00ea8d0b66d48e18853711ff88ca994f0ce Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 14 Feb 2019 08:48:37 -0800 Subject: [PATCH 001/132] Standalone Tags - Checkpoint --- interface/resources/qml/hifi/Card.qml | 20 ++++ .../hifi/commerce/marketplace/Marketplace.qml | 5 +- .../commerce/marketplace/MarketplaceItem.qml | 97 +++++++++++++++---- .../marketplace/MarketplaceListItem.qml | 33 ++++++- .../hifi/commerce/purchases/PurchasedItem.qml | 21 ++++ .../qml/hifi/commerce/purchases/Purchases.qml | 2 + 6 files changed, 158 insertions(+), 20 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 238c26236f..67abc1c3f9 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -39,6 +39,7 @@ Item { property bool isConcurrency: action === 'concurrency'; property bool isAnnouncement: action === 'announcement'; property bool isStacked: !isConcurrency && drillDownToPlace; + property bool standaloneOptimized: true; property int textPadding: 10; property int smallMargin: 4; @@ -281,7 +282,26 @@ Item { right: parent.right; margins: smallMargin; } + } + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + right: actionIcon.left + verticalCenter: parent.verticalCenter + bottom: parent.bottom; + } + height: root.standaloneOptimized ? 34 : 0 + + visible: standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight + } + function go() { Tablet.playSound(TabletEnums.ButtonClick); goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 0d42cb599e..ba83b0b61f 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -122,6 +122,8 @@ Rectangle { marketplaceItem.license = result.data.license; marketplaceItem.available = result.data.availability === "available"; marketplaceItem.created_at = result.data.created_at; + marketplaceItem.standaloneOptimized = result.data.standalone_optimized; + marketplaceItem.standaloneVisible = result.data.standalone_optimized || result.data.standalone_incompatible; marketplaceItemScrollView.contentHeight = marketplaceItemContent.height; itemsList.visible = false; marketplaceItemView.visible = true; @@ -534,7 +536,8 @@ Rectangle { price: model.cost available: model.availability === "available" isLoggedIn: root.isLoggedIn; - + standaloneOptimized: model.standalone_optimized + onShowItem: { MarketplaceScriptingInterface.getMarketplaceItem(item_id); } diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index 0a57e56099..9784e422f1 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -39,9 +39,11 @@ Rectangle { property string posted: "" property bool available: false property string created_at: "" - property bool isLoggedIn: false; - property int edition: -1; - property bool supports3DHTML: false; + property bool isLoggedIn: false + property int edition: -1 + property bool supports3DHTML: false + property bool standaloneVisible: false + property bool standaloneOptimized: false onCategoriesChanged: { @@ -50,16 +52,6 @@ Rectangle { categoriesListModel.append({"category":category}); }); } - - onDescriptionChanged: { - - if(root.supports3DHTML) { - descriptionTextModel.clear(); - descriptionTextModel.append({text: description}); - } else { - descriptionText.text = description; - } - } onAttributionsChanged: { attributionsModel.clear(); @@ -246,11 +238,38 @@ Rectangle { right: parent.right; top: itemImage.bottom; } - height: categoriesList.y - buyButton.y + categoriesList.height + height: categoriesList.y - badges.y + categoriesList.height function evalHeight() { - height = categoriesList.y - buyButton.y + categoriesList.height; - console.log("HEIGHT: " + height); + height = categoriesList.y - badges.y + categoriesList.height; + } + + Item { + id: badges + + anchors { + left: parent.left + top: parent.top + right: parent.right + } + height: childrenRect.height + + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + } + height: root.standaloneOptimized ? 34 : 0 + + visible: root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight + } } HifiControlsUit.Button { @@ -258,7 +277,7 @@ Rectangle { anchors { right: parent.right - top: parent.top + top: badges.bottom left: parent.left topMargin: 15 } @@ -529,13 +548,55 @@ Rectangle { } } } + + Item { + id: standaloneItem + + anchors { + top: licenseItem.bottom + topMargin: 15 + left: parent.left + right: parent.right + } + height: root.standaloneVisible ? childrenRect.height : 0 + + visible: root.standaloneVisible + + RalewaySemiBold { + id: standaloneLabel + + anchors.top: parent.top + anchors.left: parent.left + width: paintedWidth + height: 20 + + text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED:" : "STAND-ALONE INCOMPATIBLE:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter + } + + RalewaySemiBold { + id: standaloneOptimizedText + + anchors.top: standaloneLabel.bottom + anchors.left: parent.left + anchors.topMargin: 5 + width: paintedWidth + + text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices" + size: 14 + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter + } + } Item { id: descriptionItem property string text: "" anchors { - top: licenseItem.bottom + top: standaloneItem.bottom topMargin: 15 left: parent.left right: parent.right diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml index 2f37637e40..9da6d8552e 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -35,7 +35,8 @@ Rectangle { property string category: "" property int price: 0 property bool available: false - property bool isLoggedIn: false; + property bool isLoggedIn: false + property bool standaloneOptimized: false signal buy() signal showItem() @@ -288,8 +289,38 @@ Rectangle { onClicked: root.categoryClicked(root.category); } } + Item { + id: badges + + anchors { + left: parent.left + top: categoryLabel.bottom + right: buyButton.left + } + height: childrenRect.height + + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + left: parent.left + leftMargin: 15 + verticalCenter: parent.verticalCenter + + } + height: 24 + + visible: root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 24 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight + } + } HifiControlsUit.Button { + id: buyButton anchors { right: parent.right top: parent.top diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index df6e216b32..ec49b596bc 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -50,6 +50,8 @@ Item { property string upgradeTitle; property bool updateAvailable: root.updateItemId && root.updateItemId !== ""; property bool valid; + property bool standaloneOptimized; + property bool standaloneIncompatible; property string originalStatusText; property string originalStatusColor; @@ -838,6 +840,25 @@ Item { root.sendToPurchases({ method: 'flipCard' }); } } + } + + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + right: parent.right + bottom: parent.bottom + rightMargin: 15 + bottomMargin:12 + } + height: root.standaloneOptimized ? 34 : 0 + + visible: root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index dc892e6640..a5446202a8 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -553,6 +553,8 @@ Rectangle { upgradeTitle: model.upgrade_title; itemType: model.item_type; valid: model.valid; + standaloneOptimized: model.standalone_optimized + standaloneIncompatible: model.standalone_incompatible anchors.topMargin: 10; anchors.bottomMargin: 10; From be98fb1ac763b5ad305ab0819cebb38f17afb9b7 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 15 Feb 2019 11:21:40 -0800 Subject: [PATCH 002/132] Standalone Tags - checkpoint --- .../InspectionCertificate.qml | 63 +++++++++++++++++-- .../hifi/commerce/purchases/PurchasedItem.qml | 13 +++- .../qml/hifi/commerce/purchases/Purchases.qml | 19 +++++- interface/src/Application.cpp | 8 +++ .../PlatformInfoScriptingInterface.cpp | 12 +++- .../PlatformInfoScriptingInterface.h | 8 ++- .../shared/src/shared/GlobalAppProperties.cpp | 1 + .../shared/src/shared/GlobalAppProperties.h | 1 + 8 files changed, 113 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 8ca34af28a..16faf2feb7 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -30,6 +30,8 @@ Rectangle { property string dateAcquired: "--"; property string itemCost: "--"; property string marketplace_item_id: ""; + property bool standaloneOptimized: false; + property bool standaloneIncompatible: false; property string certTitleTextColor: hifi.colors.darkGray; property string certTextColor: hifi.colors.white; property string infoTextColor: hifi.colors.blueAccent; @@ -71,6 +73,8 @@ Rectangle { } else { root.marketplace_item_id = result.data.marketplace_item_id; root.isMyCert = result.isMyCert ? result.isMyCert : false; + root.standaloneOptimized = result.data.standalone_optimized; + root.standaloneIncompatible = result.data.standalone_incompatible; if (root.certInfoReplaceMode > 3) { root.itemName = result.data.marketplace_item_name; @@ -421,6 +425,24 @@ Rectangle { anchors.rightMargin: 24; height: root.useGoldCert ? 220 : 372; + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + right: parent.right + top: ownedByHeader.top + rightMargin: 15 + topMargin: 28 + } + + visible: root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: hifi.colors.blueHighlight + } + RalewayRegular { id: errorText; visible: !root.useGoldCert; @@ -467,6 +489,7 @@ Rectangle { color: root.infoTextColor; elide: Text.ElideRight; } + AnonymousProRegular { id: isMyCertText; visible: root.isMyCert && ownedBy.text !== "--" && ownedBy.text !== ""; @@ -485,14 +508,46 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } + RalewayRegular { + id: standaloneHeader; + text: root.standaloneOptimized ? "STAND-ALONE OPTIMIZED" : "STAND-ALONE INCOMPATIBLE"; + // Text size + size: 16; + // Anchors + anchors.top: ownedBy.bottom; + anchors.topMargin: 15; + anchors.left: parent.left; + anchors.right: parent.right; + visible: root.standaloneOptimized || root.standaloneIncompatible; + height: visible ? paintedHeight : 0; + // Style + color: hifi.colors.darkGray; + } + + RalewayRegular { + id: standaloneText; + text: root.standaloneOptimized ? "This item is stand-alone optimized" : "This item is incompatible with stand-alone devices"; + // Text size + size: 18; + // Anchors + anchors.top: standaloneHeader.bottom; + anchors.topMargin: 8; + anchors.left: standaloneHeader.left; + visible: root.standaloneOptimized || root.standaloneIncompatible; + height: visible ? paintedHeight : 0; + // Style + color: root.infoTextColor; + elide: Text.ElideRight; + } + RalewayRegular { id: dateAcquiredHeader; text: "ACQUISITION DATE"; // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; - anchors.topMargin: 28; + anchors.top: standaloneText.bottom; + anchors.topMargin: 20; anchors.left: parent.left; anchors.right: parent.horizontalCenter; anchors.rightMargin: 8; @@ -521,8 +576,8 @@ Rectangle { // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; - anchors.topMargin: 28; + anchors.top: standaloneText.bottom; + anchors.topMargin: 20; anchors.left: parent.horizontalCenter; anchors.right: parent.right; height: paintedHeight; diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index ec49b596bc..22792e5727 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -405,7 +405,9 @@ Item { id: permissionExplanationText; anchors.fill: parent; text: { - if (root.itemType === "contentSet") { + if (root.standaloneIncompatible) { + "This item is incompatible with stand-alone devices. Learn more"; + } else if (root.itemType === "contentSet") { "You do not have 'Replace Content' permissions in this domain. Learn more"; } else if (root.itemType === "entity") { "You do not have 'Rez Certified' permissions in this domain. Learn more"; @@ -419,7 +421,11 @@ Item { verticalAlignment: Text.AlignVCenter; onLinkActivated: { - sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); + if(link == "#standaloneIncompatible") { + sendToPurchases({method: 'showStandaloneIncompatibleExplanation'}); + } else { + sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); + } } } } @@ -701,7 +707,8 @@ Item { anchors.bottomMargin: 8; width: 160; height: 40; - enabled: root.hasPermissionToRezThis && + enabled: !root.standaloneIncompatible && + root.hasPermissionToRezThis && MyAvatar.skeletonModelURL !== root.itemHref && !root.wornEntityID && root.valid; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index a5446202a8..4b285e5402 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -12,7 +12,7 @@ // import Hifi 1.0 as Hifi -import QtQuick 2.5 +import QtQuick 2.9 import stylesUit 1.0 import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls @@ -33,6 +33,7 @@ Rectangle { property bool purchasesReceived: false; property bool punctuationMode: false; property bool isDebuggingFirstUseTutorial: false; + property bool isStandalone: false; property string installedApps; property bool keyboardRaised: false; property int numUpdatesAvailable: 0; @@ -44,6 +45,7 @@ Rectangle { purchasesModel.getFirstPage(); Commerce.getAvailableUpdates(); } + Connections { target: Commerce; @@ -110,6 +112,11 @@ Rectangle { } } + Component.onCompleted: { + isStandalone = PlatformInfo.isStandalone(); + console.log(isStandalone ? "IS STANDALONE" : "ISN'T STANDALONE"); + } + HifiCommerceCommon.CommerceLightbox { id: lightboxPopup; z: 999; @@ -554,7 +561,7 @@ Rectangle { itemType: model.item_type; valid: model.valid; standaloneOptimized: model.standalone_optimized - standaloneIncompatible: model.standalone_incompatible + standaloneIncompatible: root.isStandalone && model.standalone_incompatible anchors.topMargin: 10; anchors.bottomMargin: 10; @@ -675,6 +682,14 @@ Rectangle { lightboxPopup.visible = false; } lightboxPopup.visible = true; + } else if (msg.method === "showStandaloneIncompatibleExplanation") { + lightboxPopup.titleText = "Stand-alone Incompatible"; + lightboxPopup.bodyText = "The item is incompatible with stand-alone devices."; + lightboxPopup.button1text = "CLOSE"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.visible = true; } else if (msg.method === "setFilterText") { filterBar.text = msg.filterText; } else if (msg.method === "flipCard") { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1029398794..bdb19b7c90 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -768,6 +768,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { bool isStore = cmdOptionExists(argc, const_cast(argv), OCULUS_STORE_ARG); qApp->setProperty(hifi::properties::OCULUS_STORE, isStore); + // emulate standalone device + static const auto STANDALONE_ARG = "--standalone"; + bool isStandalone = cmdOptionExists(argc, const_cast(argv), STANDALONE_ARG); + qApp->setProperty(hifi::properties::STANDALONE, isStandalone); + // Ignore any previous crashes if running from command line with a test script. bool inTestMode { false }; for (int i = 0; i < argc; ++i) { @@ -3029,6 +3034,9 @@ void Application::initializeUi() { }; OffscreenQmlSurface::addWhitelistContextHandler({ QUrl{ "hifi/commerce/marketplace/Marketplace.qml" }, + QUrl{ "hifi/commerce/purchases/Purchases.qml" }, + QUrl{ "hifi/commerce/wallet/Wallet.qml" }, + QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, }, platformInfoCallback); QmlContextCallback ttsCallback = [](QQmlContext* context) { diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp index b390ab7119..89d609810c 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.cpp +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -7,7 +7,7 @@ // #include "PlatformInfoScriptingInterface.h" #include "Application.h" - +#include #include #ifdef Q_OS_WIN @@ -138,6 +138,14 @@ bool PlatformInfoScriptingInterface::has3DHTML() { #if defined(Q_OS_ANDROID) return false; #else - return true; + return !qApp->property(hifi::properties::STANDALONE).toBool(); +#endif +} + +bool PlatformInfoScriptingInterface::isStandalone() { +#if defined(Q_OS_ANDROID) + return false; +#else + return qApp->property(hifi::properties::STANDALONE).toBool(); #endif } diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h index aece09b008..31f0058585 100644 --- a/interface/src/scripting/PlatformInfoScriptingInterface.h +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -68,9 +68,15 @@ public slots: /**jsdoc * Returns true if device supports 3d HTML - * @function Window.hasRift + * @function Window.has3DHTML * @returns {boolean} true if device supports 3d HTML, otherwise false.*/ bool has3DHTML(); + + /**jsdoc + * Returns true if device is standalone + * @function Window.hasRift + * @returns {boolean} true if device is a standalone device, otherwise false.*/ + bool isStandalone(); }; #endif // hifi_PlatformInfoScriptingInterface_h diff --git a/libraries/shared/src/shared/GlobalAppProperties.cpp b/libraries/shared/src/shared/GlobalAppProperties.cpp index 54e50da3ea..1fd6c191b2 100644 --- a/libraries/shared/src/shared/GlobalAppProperties.cpp +++ b/libraries/shared/src/shared/GlobalAppProperties.cpp @@ -14,6 +14,7 @@ namespace hifi { namespace properties { const char* STEAM = "com.highfidelity.launchedFromSteam"; const char* LOGGER = "com.highfidelity.logger"; const char* OCULUS_STORE = "com.highfidelity.oculusStore"; + const char* STANDALONE = "com.highfidelity.standalone"; const char* TEST = "com.highfidelity.test"; const char* TRACING = "com.highfidelity.tracing"; const char* HMD = "com.highfidelity.hmd"; diff --git a/libraries/shared/src/shared/GlobalAppProperties.h b/libraries/shared/src/shared/GlobalAppProperties.h index 174be61939..6809d5530a 100644 --- a/libraries/shared/src/shared/GlobalAppProperties.h +++ b/libraries/shared/src/shared/GlobalAppProperties.h @@ -16,6 +16,7 @@ namespace hifi { namespace properties { extern const char* STEAM; extern const char* LOGGER; extern const char* OCULUS_STORE; + extern const char* STANDALONE; extern const char* TEST; extern const char* TRACING; extern const char* HMD; From 7446fa9e7eb81ae3476b70baea885518b6cc54a9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 15 Feb 2019 11:21:06 -0800 Subject: [PATCH 003/132] allow mesh shapes to use MOTION_TYPE_KINEMATIC --- libraries/physics/src/EntityMotionState.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index ce9cb20c21..91c4c43c1d 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -205,6 +205,16 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH || (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) { + if (_entity->isMoving()) { + // DANGER: Bullet doesn't support non-zero velocity for these shapes --> collision details may be wrong + // in ways that allow other DYNAMIC objects to tunnel/penetrate/snag. + // However, in practice low-velocity collisions work OK most of the time, and if we enforce these objects + // to be MOTION_TYPE_STATIC then some other bugs can be worse (e.g. when Grabbing --> Grab Action fails) + // so we're making a tradeoff here. + // TODO: The Correct Solution is to NOT use btBvhTriangleMesh shape for moving objects and instead compute the convex + // decomposition and build a btCompoundShape with convex sub-shapes. + return MOTION_TYPE_KINEMATIC; + } return MOTION_TYPE_STATIC; } From 304f993391b9f187e7b079b837fb570175485a62 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 15 Feb 2019 16:50:45 -0800 Subject: [PATCH 004/132] remove enforcement of MOTION_TYPE_STATIC for mesh shapes --- libraries/physics/src/EntityMotionState.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 91c4c43c1d..4d210c96c5 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -203,21 +203,6 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const { } assert(entityTreeIsLocked()); - if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH - || (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) { - if (_entity->isMoving()) { - // DANGER: Bullet doesn't support non-zero velocity for these shapes --> collision details may be wrong - // in ways that allow other DYNAMIC objects to tunnel/penetrate/snag. - // However, in practice low-velocity collisions work OK most of the time, and if we enforce these objects - // to be MOTION_TYPE_STATIC then some other bugs can be worse (e.g. when Grabbing --> Grab Action fails) - // so we're making a tradeoff here. - // TODO: The Correct Solution is to NOT use btBvhTriangleMesh shape for moving objects and instead compute the convex - // decomposition and build a btCompoundShape with convex sub-shapes. - return MOTION_TYPE_KINEMATIC; - } - return MOTION_TYPE_STATIC; - } - if (_entity->getLocked()) { if (_entity->isMoving()) { return MOTION_TYPE_KINEMATIC; From 2fdc9bce7734ff32a424576bac580a3638b4d8c9 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 15 Feb 2019 16:59:18 -0800 Subject: [PATCH 005/132] remove velocity restrictions on SHAPE_TYPE_STATIC_MESH --- libraries/entities/src/EntityItem.cpp | 78 +++++++++++---------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 2c6d679b46..049b46ec7e 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1912,25 +1912,19 @@ void EntityItem::setRotation(glm::quat rotation) { void EntityItem::setVelocity(const glm::vec3& value) { glm::vec3 velocity = getLocalVelocity(); if (velocity != value) { - if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { - if (velocity != Vectors::ZERO) { - setLocalVelocity(Vectors::ZERO); - } - } else { - float speed = glm::length(value); - if (!glm::isnan(speed)) { - const float MIN_LINEAR_SPEED = 0.001f; - const float MAX_LINEAR_SPEED = 270.0f; // 3m per step at 90Hz - if (speed < MIN_LINEAR_SPEED) { - velocity = ENTITY_ITEM_ZERO_VEC3; - } else if (speed > MAX_LINEAR_SPEED) { - velocity = (MAX_LINEAR_SPEED / speed) * value; - } else { - velocity = value; - } - setLocalVelocity(velocity); - _flags |= Simulation::DIRTY_LINEAR_VELOCITY; + float speed = glm::length(value); + if (!glm::isnan(speed)) { + const float MIN_LINEAR_SPEED = 0.001f; + const float MAX_LINEAR_SPEED = 270.0f; // 3m per step at 90Hz + if (speed < MIN_LINEAR_SPEED) { + velocity = ENTITY_ITEM_ZERO_VEC3; + } else if (speed > MAX_LINEAR_SPEED) { + velocity = (MAX_LINEAR_SPEED / speed) * value; + } else { + velocity = value; } + setLocalVelocity(velocity); + _flags |= Simulation::DIRTY_LINEAR_VELOCITY; } } } @@ -1948,19 +1942,15 @@ void EntityItem::setDamping(float value) { void EntityItem::setGravity(const glm::vec3& value) { withWriteLock([&] { if (_gravity != value) { - if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { - _gravity = Vectors::ZERO; - } else { - float magnitude = glm::length(value); - if (!glm::isnan(magnitude)) { - const float MAX_ACCELERATION_OF_GRAVITY = 10.0f * 9.8f; // 10g - if (magnitude > MAX_ACCELERATION_OF_GRAVITY) { - _gravity = (MAX_ACCELERATION_OF_GRAVITY / magnitude) * value; - } else { - _gravity = value; - } - _flags |= Simulation::DIRTY_LINEAR_VELOCITY; + float magnitude = glm::length(value); + if (!glm::isnan(magnitude)) { + const float MAX_ACCELERATION_OF_GRAVITY = 10.0f * 9.8f; // 10g + if (magnitude > MAX_ACCELERATION_OF_GRAVITY) { + _gravity = (MAX_ACCELERATION_OF_GRAVITY / magnitude) * value; + } else { + _gravity = value; } + _flags |= Simulation::DIRTY_LINEAR_VELOCITY; } } }); @@ -1969,23 +1959,19 @@ void EntityItem::setGravity(const glm::vec3& value) { void EntityItem::setAngularVelocity(const glm::vec3& value) { glm::vec3 angularVelocity = getLocalAngularVelocity(); if (angularVelocity != value) { - if (getShapeType() == SHAPE_TYPE_STATIC_MESH) { - setLocalAngularVelocity(Vectors::ZERO); - } else { - float speed = glm::length(value); - if (!glm::isnan(speed)) { - const float MIN_ANGULAR_SPEED = 0.0002f; - const float MAX_ANGULAR_SPEED = 9.0f * TWO_PI; // 1/10 rotation per step at 90Hz - if (speed < MIN_ANGULAR_SPEED) { - angularVelocity = ENTITY_ITEM_ZERO_VEC3; - } else if (speed > MAX_ANGULAR_SPEED) { - angularVelocity = (MAX_ANGULAR_SPEED / speed) * value; - } else { - angularVelocity = value; - } - setLocalAngularVelocity(angularVelocity); - _flags |= Simulation::DIRTY_ANGULAR_VELOCITY; + float speed = glm::length(value); + if (!glm::isnan(speed)) { + const float MIN_ANGULAR_SPEED = 0.0002f; + const float MAX_ANGULAR_SPEED = 9.0f * TWO_PI; // 1/10 rotation per step at 90Hz + if (speed < MIN_ANGULAR_SPEED) { + angularVelocity = ENTITY_ITEM_ZERO_VEC3; + } else if (speed > MAX_ANGULAR_SPEED) { + angularVelocity = (MAX_ANGULAR_SPEED / speed) * value; + } else { + angularVelocity = value; } + setLocalAngularVelocity(angularVelocity); + _flags |= Simulation::DIRTY_ANGULAR_VELOCITY; } } } From dca166f8219909e1a1f921638d1fc4794ec66b35 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Wed, 20 Feb 2019 14:23:54 -0800 Subject: [PATCH 006/132] Stand-alone Optimized Tagging - Add tagging for GOTO User Stories --- interface/resources/qml/hifi/Card.qml | 39 ++++++++++--------- interface/resources/qml/hifi/Feed.qml | 2 + .../hifi/commerce/marketplace/Marketplace.qml | 8 ++++ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 67abc1c3f9..9beb108b36 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -30,6 +30,7 @@ Item { property string imageUrl: ""; property var goFunction: null; property string storyId: ""; + property bool standaloneOptimized: false; property bool drillDownToPlace: false; property bool showPlace: isConcurrency; @@ -39,7 +40,7 @@ Item { property bool isConcurrency: action === 'concurrency'; property bool isAnnouncement: action === 'announcement'; property bool isStacked: !isConcurrency && drillDownToPlace; - property bool standaloneOptimized: true; + property int textPadding: 10; property int smallMargin: 4; @@ -271,6 +272,24 @@ Item { goFunction("hifi://" + hifiUrl); } } + + HiFiGlyphs { + id: standaloneOptomizedBadge + + anchors { + right: actionIcon.left + bottom: parent.bottom; + } + height: (root.isConcurrency && root.standaloneOptimized) ? 34 : 0 + + visible: root.isConcurrency && root.standaloneOptimized + + text: hifi.glyphs.hmd + size: 34 + horizontalAlignment: Text.AlignHCenter + color: messageColor + } + StateImage { id: actionIcon; visible: !isAnnouncement; @@ -282,24 +301,6 @@ Item { right: parent.right; margins: smallMargin; } - - } - HiFiGlyphs { - id: standaloneOptomizedBadge - - anchors { - right: actionIcon.left - verticalCenter: parent.verticalCenter - bottom: parent.bottom; - } - height: root.standaloneOptimized ? 34 : 0 - - visible: standaloneOptimized - - text: hifi.glyphs.hmd - size: 34 - horizontalAlignment: Text.AlignHCenter - color: hifi.colors.blueHighlight } function go() { diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 1e89971938..68aab2fdd2 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -82,6 +82,7 @@ Column { action: data.action || "", thumbnail_url: resolveUrl(thumbnail_url), image_url: resolveUrl(data.details && data.details.image_url), + standalone_optimized: data.standalone_optimized, metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. @@ -127,6 +128,7 @@ Column { hifiUrl: model.place_name + model.path; thumbnail: model.thumbnail_url; imageUrl: model.image_url; + standaloneOptimized: model.standalone_optimized; action: model.action; timestamp: model.created_at; onlineUsers: model.online_users; diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 8c7e050ec2..5f97e64e6c 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -90,6 +90,14 @@ Rectangle { id: -1, name: "Everything" }); + categoriesModel.append({ + id: -1, + name: "Standalone Optimized" + }); + categoriesModel.append({ + id: -1, + name: "Standalone Compatible" + }); result.data.items.forEach(function(category) { categoriesModel.append({ id: category.id, From 292ac88c961b2762acc6919749132963fba884e3 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Mon, 25 Feb 2019 11:36:45 -0800 Subject: [PATCH 007/132] Update with new standalone-optimized icon --- .../resources/icons/standalone-optimized.svg | 27 ++++++++ interface/resources/qml/hifi/Card.qml | 28 ++++---- .../hifi/commerce/marketplace/Marketplace.qml | 20 +++--- .../commerce/marketplace/MarketplaceItem.qml | 38 ++++++----- .../marketplace/MarketplaceListItem.qml | 66 ++++++++++--------- .../hifi/commerce/purchases/PurchasedItem.qml | 18 +++-- 6 files changed, 123 insertions(+), 74 deletions(-) create mode 100644 interface/resources/icons/standalone-optimized.svg diff --git a/interface/resources/icons/standalone-optimized.svg b/interface/resources/icons/standalone-optimized.svg new file mode 100644 index 0000000000..f721be9ebb --- /dev/null +++ b/interface/resources/icons/standalone-optimized.svg @@ -0,0 +1,27 @@ + + + + + + diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 9beb108b36..1c0424a691 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -273,22 +273,28 @@ Item { } } - HiFiGlyphs { + Image { id: standaloneOptomizedBadge anchors { right: actionIcon.left - bottom: parent.bottom; + top: actionIcon.top + topMargin: 2 + rightMargin: 3 } - height: (root.isConcurrency && root.standaloneOptimized) ? 34 : 0 - - visible: root.isConcurrency && root.standaloneOptimized - - text: hifi.glyphs.hmd - size: 34 - horizontalAlignment: Text.AlignHCenter - color: messageColor - } + height: root.standaloneOptimized ? 25 : 0 + width: 25 + + visible: root.standaloneOptimized && isConcurrency + fillMode: Image.PreserveAspectFit + source: "../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge + color: hifi.colors.blueHighlight + visible: root.standaloneOptimized && isConcurrency + } StateImage { id: actionIcon; diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 5f97e64e6c..2206dfcb99 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -196,16 +196,16 @@ Rectangle { visible: true Image { - id: marketplaceHeaderImage; - source: "../common/images/marketplaceHeaderImage.png"; - anchors.top: parent.top; - anchors.topMargin: 2; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 0; - anchors.left: parent.left; - anchors.leftMargin: 8; - width: 140; - fillMode: Image.PreserveAspectFit; + id: marketplaceHeaderImage + source: "../common/images/marketplaceHeaderImage.png" + anchors.top: parent.top + anchors.topMargin: 2 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 8 + width: 140 + fillMode: Image.PreserveAspectFit MouseArea { anchors.fill: parent; diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml index ecd0c8b422..1579d70500 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceItem.qml @@ -15,6 +15,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.9 import QtQuick.Controls 2.2 import stylesUit 1.0 +import QtGraphicalEffects 1.0 import controlsUit 1.0 as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon @@ -248,28 +249,33 @@ Rectangle { id: badges anchors { - left: parent.left + right: buyButton.left top: parent.top - right: parent.right + rightMargin: 10 } height: childrenRect.height - HiFiGlyphs { + Image { id: standaloneOptomizedBadge anchors { - left: parent.left - verticalCenter: parent.verticalCenter + topMargin: 15 + right: parent.right + top: parent.top } - height: root.standaloneOptimized ? 34 : 0 + height: root.standaloneOptimized ? 50 : 0 + width: 50 visible: root.standaloneOptimized - - text: hifi.glyphs.hmd - size: 34 - horizontalAlignment: Text.AlignHCenter + fillMode: Image.PreserveAspectFit + source: "../../../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge color: hifi.colors.blueHighlight - } + visible: root.standaloneOptimized + } } HifiControlsUit.Button { @@ -277,11 +283,11 @@ Rectangle { anchors { right: parent.right - top: badges.bottom - left: parent.left + top: parent.top topMargin: 15 } - height: 50 + height: 50 + width: 180 property bool isNFS: availability === "not for sale" // Note: server will say "sold out" or "invalidated" before it says NFS property bool isMine: creator === Account.username @@ -301,11 +307,11 @@ Rectangle { id: creatorItem anchors { - top: buyButton.bottom + top: parent.top leftMargin: 15 topMargin: 15 } - width: parent.width + width: paintedWidth height: childrenRect.height RalewaySemiBold { diff --git a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml index 4fbed690f6..d0632ce8c3 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/MarketplaceListItem.qml @@ -240,16 +240,18 @@ Rectangle { id: creatorText anchors { - top: creatorLabel.top; - left: creatorLabel.right; - leftMargin: 15; + top: creatorLabel.top + left: creatorLabel.right + leftMargin: 15 + right: badges.left } - width: paintedWidth; + width: paintedWidth - text: root.creator; - size: 14; - color: hifi.colors.lightGray; - verticalAlignment: Text.AlignVCenter; + text: root.creator + size: 14 + elide: Text.ElideRight + color: hifi.colors.lightGray + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { @@ -260,12 +262,12 @@ Rectangle { left: parent.left leftMargin: 15 } - width: paintedWidth; + width: paintedWidth - text: "IN:"; - size: 14; - color: hifi.colors.lightGrayText; - verticalAlignment: Text.AlignVCenter; + text: "IN:" + size: 14 + color: hifi.colors.lightGrayText + verticalAlignment: Text.AlignVCenter } RalewaySemiBold { @@ -275,14 +277,15 @@ Rectangle { top: categoryLabel.top left: categoryLabel.right leftMargin: 15 + right: badges.left } width: paintedWidth text: root.category size: 14 - color: hifi.colors.blueHighlight; - verticalAlignment: Text.AlignVCenter; - + color: hifi.colors.blueHighlight + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight MouseArea { anchors.fill: parent @@ -293,30 +296,33 @@ Rectangle { id: badges anchors { - left: parent.left - top: categoryLabel.bottom right: buyButton.left + top: parent.top + topMargin: 10 + rightMargin: 10 } - height: childrenRect.height + height: 50 - HiFiGlyphs { + Image { id: standaloneOptomizedBadge anchors { - left: parent.left - leftMargin: 15 - verticalCenter: parent.verticalCenter - + right: parent.right + top: parent.top } - height: 24 + height: root.standaloneOptimized ? 40 : 0 + width: 40 visible: root.standaloneOptimized - - text: hifi.glyphs.hmd - size: 24 - horizontalAlignment: Text.AlignHCenter + fillMode: Image.PreserveAspectFit + source: "../../../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge color: hifi.colors.blueHighlight - } + visible: root.standaloneOptimized + } } HifiControlsUit.Button { diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 22792e5727..87bf7afbe4 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -13,6 +13,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.5 +import QtGraphicalEffects 1.0 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import stylesUit 1.0 @@ -848,8 +849,7 @@ Item { } } } - - HiFiGlyphs { + Image { id: standaloneOptomizedBadge anchors { @@ -858,14 +858,18 @@ Item { rightMargin: 15 bottomMargin:12 } - height: root.standaloneOptimized ? 34 : 0 + height: root.standaloneOptimized ? 36 : 0 + width: 36 visible: root.standaloneOptimized - - text: hifi.glyphs.hmd - size: 34 - horizontalAlignment: Text.AlignHCenter + fillMode: Image.PreserveAspectFit + source: "../../../../icons/standalone-optimized.svg" + } + ColorOverlay { + anchors.fill: standaloneOptomizedBadge + source: standaloneOptomizedBadge color: hifi.colors.blueHighlight + visible: root.standaloneOptimized } } From 7912c1b2fdaa17acb14b0c452209913921405aba Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Tue, 26 Feb 2019 08:38:07 -0500 Subject: [PATCH 008/132] Adds Ubuntu 16.04 friendly cmake instructions --- BUILD_LINUX.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 1559ece191..58368a168f 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -37,8 +37,14 @@ sudo apt-get -y install libpulse0 libnss3 libnspr4 libfontconfig1 libxcursor1 li Install build tools: ```bash +# For Ubuntu 18.04 sudo apt-get install cmake ``` +```bash +# For Ubuntu 16.04 +wget https://cmake.org/files/v3.9/cmake-3.9.5-Linux-x86_64.sh +sudo sh cmake-3.9.5-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir +``` Install Python 3: ```bash From 79c5cfee53928b559c04b89a49a00583e54d1e8f Mon Sep 17 00:00:00 2001 From: Flame Soulis Date: Tue, 26 Feb 2019 08:38:28 -0500 Subject: [PATCH 009/132] Updates checkout tag to the latest (0.79) --- BUILD_LINUX.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 58368a168f..8820bda8f6 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -67,7 +67,7 @@ git tags Then checkout last tag with: ```bash -git checkout tags/v0.71.0 +git checkout tags/v0.79.0 ``` ### Compiling From 18325ef5e3771f8752f2b541a8e8455dd85e99e1 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Mon, 16 Apr 2018 15:26:06 +0300 Subject: [PATCH 010/132] Save the "Mute Microphone" setting --- interface/src/scripting/Audio.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..b4e3d7913b 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -25,6 +25,8 @@ QString Audio::DESKTOP { "Desktop" }; QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; +Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false }; + float Audio::loudnessToLevel(float loudness) { float level = loudness * (1/32768.0f); // level in [0, 1] @@ -42,6 +44,7 @@ Audio::Audio() : _devices(_contextIsHMD) { connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); onContextChanged(); + setMuted(mutedSetting.get()); } bool Audio::startRecording(const QString& filepath) { @@ -89,6 +92,15 @@ bool Audio::noiseReductionEnabled() const { }); } +void Audio::onMutedChanged() { + bool isMuted = DependencyManager::get()->isMuted(); + if (_isMuted != isMuted) { + _isMuted = isMuted; + mutedSetting.set(_isMuted); + emit mutedChanged(_isMuted); + } +} + void Audio::enableNoiseReduction(bool enable) { bool changed = false; withWriteLock([&] { From 3f523617535a991463334977b1c4c164dbcfb17d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 17 Feb 2019 14:21:23 -0800 Subject: [PATCH 011/132] rework audioMuteOverlay.js --- interface/src/scripting/Audio.cpp | 10 +-- scripts/defaultScripts.js | 3 +- scripts/system/audioMuteOverlay.js | 140 +++++++++++++---------------- 3 files changed, 63 insertions(+), 90 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index b4e3d7913b..bb40f69b0b 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -76,6 +76,7 @@ void Audio::setMuted(bool isMuted) { withWriteLock([&] { if (_isMuted != isMuted) { _isMuted = isMuted; + mutedSetting.set(_isMuted); auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); changed = true; @@ -92,15 +93,6 @@ bool Audio::noiseReductionEnabled() const { }); } -void Audio::onMutedChanged() { - bool isMuted = DependencyManager::get()->isMuted(); - if (_isMuted != isMuted) { - _isMuted = isMuted; - mutedSetting.set(_isMuted); - emit mutedChanged(_isMuted); - } -} - void Audio::enableNoiseReduction(bool enable) { bool changed = false; withWriteLock([&] { diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index bd7e79dffc..e392680df9 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/miniTablet.js" + "system/miniTablet.js", + "system/audioMuteOverlay.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 731d62017d..14ac96c8c6 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -1,104 +1,84 @@ -"use strict"; -/* jslint vars: true, plusplus: true, forin: true*/ -/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */ -/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // audioMuteOverlay.js // // client script that creates an overlay to provide mute feedback // // Created by Triplelexx on 17/03/09 +// Reworked by Seth Alves on 2019-2-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 // +"use strict"; + +/* global Audio, Script, Overlays, Quat, MyAvatar */ + (function() { // BEGIN LOCAL_SCOPE - var utilsPath = Script.resolvePath('../developer/libraries/utils.js'); - Script.include(utilsPath); - var TWEEN_SPEED = 0.025; - var MIX_AMOUNT = 0.25; + var lastInputLoudness = 0.0; + var sampleRate = 8.0; // Hz + var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)) // 500 milliseconds attack + var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)) // 1000 milliseconds release + var holdReset = 2.0 * sampleRate; // 2 seconds hold + var holdCount = 0; + var warningOverlayID = null; - var overlayPosition = Vec3.ZERO; - var tweenPosition = 0; - var startColor = { - red: 170, - green: 170, - blue: 170 - }; - var endColor = { - red: 255, - green: 0, - blue: 0 - }; - var overlayID; + function showWarning() { + if (warningOverlayID) { + return; + } + warningOverlayID = Overlays.addOverlay("text3d", { + name: "Muted-Warning", + localPosition: { x: 0.2, y: -0.35, z: -1.0 }, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), + text: "Warning: you are muted", + textAlpha: 1, + color: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + lineHeight: 0.042, + visible: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") + }); + }; - Script.update.connect(update); - Script.scriptEnding.connect(cleanup); + function hideWarning() { + if (!warningOverlayID) { + return; + } + Overlays.deleteOverlay(warningOverlayID); + warningOverlayID = null; + } - function update(dt) { - if (!Audio.muted) { - if (hasOverlay()) { - deleteOverlay(); - } - } else if (!hasOverlay()) { - createOverlay(); - } else { - updateOverlay(); - } - } + function cleanup() { + Overlays.deleteOverlay(warningOverlayID); + } - function getOffsetPosition() { - return Vec3.sum(Camera.position, Quat.getFront(Camera.orientation)); - } + Script.scriptEnding.connect(cleanup); - function createOverlay() { - overlayPosition = getOffsetPosition(); - overlayID = Overlays.addOverlay("sphere", { - position: overlayPosition, - rotation: Camera.orientation, - alpha: 0.9, - dimensions: 0.1, - solid: true, - ignoreRayIntersection: true - }); - } + Script.setInterval(function() { - function hasOverlay() { - return Overlays.getProperty(overlayID, "position") !== undefined; - } + var inputLoudness = Audio.inputLevel; + var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; + inputLoudness += tc * (lastInputLoudness - inputLoudness); + lastInputLoudness = inputLoudness; - function updateOverlay() { - // increase by TWEEN_SPEED until completion - if (tweenPosition < 1) { - tweenPosition += TWEEN_SPEED; - } else { - // after tween completion reset to zero and flip values to ping pong - tweenPosition = 0; - for (var component in startColor) { - var storedColor = startColor[component]; - startColor[component] = endColor[component]; - endColor[component] = storedColor; - } - } - // mix previous position with new and mix colors - overlayPosition = Vec3.mix(overlayPosition, getOffsetPosition(), MIX_AMOUNT); - Overlays.editOverlay(overlayID, { - color: colorMix(startColor, endColor, easeIn(tweenPosition)), - position: overlayPosition, - rotation: Camera.orientation - }); - } + if (Audio.muted && inputLoudness > 0.3) { + holdCount = holdReset; + } else { + holdCount = Math.max(holdCount - 1, 0); + } - function deleteOverlay() { - Overlays.deleteOverlay(overlayID); - } + if (holdCount > 0) { + showWarning(); + } else { + hideWarning(); + } + }, 1000.0 / sampleRate); - function cleanup() { - deleteOverlay(); - Audio.muted.disconnect(onMuteToggled); - Script.update.disconnect(update); - } }()); // END LOCAL_SCOPE From 38256df0f24cd710c2e8e32683fa73de9081361f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 19 Feb 2019 09:32:41 -0800 Subject: [PATCH 012/132] add a way to disble muted warning from audio panel. fix positioning of warning. hide warning when removing timer. --- interface/resources/qml/hifi/audio/Audio.qml | 13 ++ interface/src/scripting/Audio.cpp | 25 ++++ interface/src/scripting/Audio.h | 14 +- libraries/audio-client/src/AudioClient.cpp | 8 + libraries/audio-client/src/AudioClient.h | 5 + scripts/system/audioMuteOverlay.js | 146 ++++++++++++------- 6 files changed, 155 insertions(+), 56 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..34ae64aee8 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -159,6 +159,19 @@ Rectangle { onXChanged: rightMostInputLevelPos = x + width } } + + RowLayout { + spacing: muteMic.spacing*2; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Warn when muted"); + checked: AudioScriptingInterface.warnWhenMuted; + onClicked: { + AudioScriptingInterface.warnWhenMuted = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding + } + } + } } Separator {} diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index bb40f69b0b..4a4b3c146b 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -25,6 +25,7 @@ QString Audio::DESKTOP { "Desktop" }; QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; +Setting::Handle enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true }; Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false }; @@ -39,10 +40,12 @@ Audio::Audio() : _devices(_contextIsHMD) { auto client = DependencyManager::get().data(); connect(client, &AudioClient::muteToggled, this, &Audio::setMuted); connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction); + connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted); connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); + enableWarnWhenMuted(enableWarnWhenMutedSetting.get()); onContextChanged(); setMuted(mutedSetting.get()); } @@ -109,6 +112,28 @@ void Audio::enableNoiseReduction(bool enable) { } } +bool Audio::warnWhenMutedEnabled() const { + return resultWithReadLock([&] { + return _enableWarnWhenMuted; + }); +} + +void Audio::enableWarnWhenMuted(bool enable) { + bool changed = false; + withWriteLock([&] { + if (_enableWarnWhenMuted != enable) { + _enableWarnWhenMuted = enable; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setWarnWhenMuted", Q_ARG(bool, enable), Q_ARG(bool, false)); + enableWarnWhenMutedSetting.set(enable); + changed = true; + } + }); + if (changed) { + emit warnWhenMutedChanged(enable); + } +} + float Audio::getInputVolume() const { return resultWithReadLock([&] { return _inputVolume; diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..7e216eb0b2 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -58,6 +58,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) + Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged) Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) @@ -75,6 +76,7 @@ public: bool isMuted() const; bool noiseReductionEnabled() const; + bool warnWhenMutedEnabled() const; float getInputVolume() const; float getInputLevel() const; bool isClipping() const; @@ -192,7 +194,7 @@ signals: * }); */ void mutedChanged(bool isMuted); - + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -201,6 +203,14 @@ signals: */ void noiseReductionChanged(bool isEnabled); + /**jsdoc + * Triggered when "warn when muted" is enabled or disabled. + * @function Audio.warnWhenMutedChanged + * @param {boolean} isEnabled - true if "warn when muted" is enabled, otherwise false. + * @returns {Signal} + */ + void warnWhenMutedChanged(bool isEnabled); + /**jsdoc * Triggered when the input audio volume changes. * @function Audio.inputVolumeChanged @@ -248,6 +258,7 @@ public slots: private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); + void enableWarnWhenMuted(bool enable); void setInputVolume(float volume); void onInputLoudnessChanged(float loudness, bool isClipping); @@ -262,6 +273,7 @@ private: bool _isClipping { false }; bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. + bool _enableWarnWhenMuted { true }; bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 8c50a195ee..1c10d24f23 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1531,6 +1531,14 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) { } } +void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) { + if (_warnWhenMuted != enable) { + _warnWhenMuted = enable; + if (emitSignal) { + emit warnWhenMutedChanged(_warnWhenMuted); + } + } +} bool AudioClient::setIsStereoInput(bool isStereoInput) { bool stereoInputChanged = false; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 29036b7c71..6d3483b0f8 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -210,6 +210,9 @@ public slots: void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true); bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } + void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true); + bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; } + bool getLocalEcho() { return _shouldEchoLocally; } void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; } void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } @@ -246,6 +249,7 @@ signals: void inputVolumeChanged(float volume); void muteToggled(bool muted); void noiseReductionChanged(bool noiseReductionEnabled); + void warnWhenMutedChanged(bool warnWhenMutedEnabled); void mutedByMixer(); void inputReceived(const QByteArray& inputSamples); void inputLoudnessChanged(float loudness, bool isClipping); @@ -365,6 +369,7 @@ private: bool _shouldEchoLocally; bool _shouldEchoToServer; bool _isNoiseGateEnabled; + bool _warnWhenMuted; bool _reverb; AudioEffectOptions _scriptReverbOptions; diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 14ac96c8c6..d759b7d885 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -17,68 +17,104 @@ (function() { // BEGIN LOCAL_SCOPE - var lastInputLoudness = 0.0; - var sampleRate = 8.0; // Hz - var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)) // 500 milliseconds attack - var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)) // 1000 milliseconds release - var holdReset = 2.0 * sampleRate; // 2 seconds hold - var holdCount = 0; - var warningOverlayID = null; + var lastInputLoudness = 0.0; + var sampleRate = 8.0; // Hz + var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack + var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + var holdReset = 2.0 * sampleRate; // 2 seconds hold + var holdCount = 0; + var warningOverlayID = null; + var pollInterval = null; + var warningText = "Muted"; + var textDimensions = { x: 100, y: 50 }; - function showWarning() { - if (warningOverlayID) { - return; - } - warningOverlayID = Overlays.addOverlay("text3d", { - name: "Muted-Warning", - localPosition: { x: 0.2, y: -0.35, z: -1.0 }, - localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), - text: "Warning: you are muted", - textAlpha: 1, - color: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - lineHeight: 0.042, - visible: true, - ignoreRayIntersection: true, - drawInFront: true, - grabbable: false, - parentID: MyAvatar.SELF_ID, - parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") - }); - }; + function showWarning() { + if (warningOverlayID) { + return; + } - function hideWarning() { - if (!warningOverlayID) { - return; - } - Overlays.deleteOverlay(warningOverlayID); - warningOverlayID = null; - } + var windowWidth; + var windowHeight; + if (HMD.active) { + var viewportDimension = Controller.getViewportDimensions(); + windowWidth = viewportDimension.x; + windowHeight = viewportDimension.y; + } else { + windowWidth = Window.innerWidth; + windowHeight = Window.innerHeight; + } - function cleanup() { - Overlays.deleteOverlay(warningOverlayID); - } + warningOverlayID = Overlays.addOverlay("text", { + name: "Muted-Warning", + font: { size: 36 }, + text: warningText, + x: windowWidth / 2 - textDimensions.x / 2, + y: windowHeight / 2 - textDimensions.y / 2, + width: textDimensions.x, + height: textDimensions.y, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + visible: true + }); + } - Script.scriptEnding.connect(cleanup); + function hideWarning() { + if (!warningOverlayID) { + return; + } + Overlays.deleteOverlay(warningOverlayID); + warningOverlayID = null; + } - Script.setInterval(function() { + function startPoll() { + if (pollInterval) { + return; + } + pollInterval = Script.setInterval(function() { + var inputLoudness = Audio.inputLevel; + var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; + inputLoudness += tc * (lastInputLoudness - inputLoudness); + lastInputLoudness = inputLoudness; - var inputLoudness = Audio.inputLevel; - var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; - inputLoudness += tc * (lastInputLoudness - inputLoudness); - lastInputLoudness = inputLoudness; + if (inputLoudness > 0.1) { + holdCount = holdReset; + } else { + holdCount = Math.max(holdCount - 1, 0); + } - if (Audio.muted && inputLoudness > 0.3) { - holdCount = holdReset; - } else { - holdCount = Math.max(holdCount - 1, 0); - } + if (holdCount > 0) { + showWarning(); + } else { + hideWarning(); + } + }, 1000.0 / sampleRate); + } - if (holdCount > 0) { - showWarning(); - } else { - hideWarning(); - } - }, 1000.0 / sampleRate); + function stopPoll() { + if (!pollInterval) { + return; + } + Script.clearInterval(pollInterval); + pollInterval = null; + hideWarning(); + } + + function startOrStopPoll() { + if (Audio.warnWhenMuted && Audio.muted) { + startPoll(); + } else { + stopPoll(); + } + } + + function cleanup() { + stopPoll(); + } + + Script.scriptEnding.connect(cleanup); + + startOrStopPoll(); + Audio.mutedChanged.connect(startOrStopPoll); + Audio.warnWhenMutedChanged.connect(startOrStopPoll); }()); // END LOCAL_SCOPE From 76aa6fb1b9a7416c6c8858c7fc9537a48ff6d14a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 20 Feb 2019 12:53:43 -0800 Subject: [PATCH 013/132] keep muted warning in center of view for HMD --- scripts/system/audioMuteOverlay.js | 51 ++++++++++++++++++------------ 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index d759b7d885..65793d1d87 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -26,36 +26,45 @@ var warningOverlayID = null; var pollInterval = null; var warningText = "Muted"; - var textDimensions = { x: 100, y: 50 }; function showWarning() { if (warningOverlayID) { return; } - var windowWidth; - var windowHeight; if (HMD.active) { - var viewportDimension = Controller.getViewportDimensions(); - windowWidth = viewportDimension.x; - windowHeight = viewportDimension.y; + warningOverlayID = Overlays.addOverlay("text3d", { + name: "Muted-Warning", + localPosition: { x: 0, y: 0, z: -1.0 }, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), + text: warningText, + textAlpha: 1, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + lineHeight: 0.042, + dimensions: { x: 0.11, y: 0.05 }, + visible: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") + }); } else { - windowWidth = Window.innerWidth; - windowHeight = Window.innerHeight; + var textDimensions = { x: 100, y: 50 }; + warningOverlayID = Overlays.addOverlay("text", { + name: "Muted-Warning", + font: { size: 36 }, + text: warningText, + x: Window.innerWidth / 2 - textDimensions.x / 2, + y: Window.innerHeight / 2 - textDimensions.y / 2, + width: textDimensions.x, + height: textDimensions.y, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + visible: true + }); } - - warningOverlayID = Overlays.addOverlay("text", { - name: "Muted-Warning", - font: { size: 36 }, - text: warningText, - x: windowWidth / 2 - textDimensions.x / 2, - y: windowHeight / 2 - textDimensions.y / 2, - width: textDimensions.x, - height: textDimensions.y, - textColor: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - visible: true - }); } function hideWarning() { From bbad6af0d692b176bef20dc87866ccda8848d04c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 20 Feb 2019 13:07:24 -0800 Subject: [PATCH 014/132] attempt to take background noise into account when triggering mute warning --- scripts/system/audioMuteOverlay.js | 33 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 65793d1d87..96f6d636dc 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -13,14 +13,22 @@ "use strict"; -/* global Audio, Script, Overlays, Quat, MyAvatar */ +/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */ (function() { // BEGIN LOCAL_SCOPE - var lastInputLoudness = 0.0; + var lastShortTermInputLoudness = 0.0; + var lastLongTermInputLoudness = 0.0; var sampleRate = 8.0; // Hz - var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack - var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + + var shortTermAttackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack + var shortTermReleaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + + var longTermAttackTC = Math.exp(-1.0 / (sampleRate * 5.0)); // 5 second attack + var longTermReleaseTC = Math.exp(-1.0 / (sampleRate * 10.0)); // 10 seconds release + + var activationThreshold = 0.05; // how much louder short-term needs to be than long-term to trigger warning + var holdReset = 2.0 * sampleRate; // 2 seconds hold var holdCount = 0; var warningOverlayID = null; @@ -80,12 +88,19 @@ return; } pollInterval = Script.setInterval(function() { - var inputLoudness = Audio.inputLevel; - var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; - inputLoudness += tc * (lastInputLoudness - inputLoudness); - lastInputLoudness = inputLoudness; + var shortTermInputLoudness = Audio.inputLevel; + var longTermInputLoudness = shortTermInputLoudness; - if (inputLoudness > 0.1) { + var shortTc = (shortTermInputLoudness > lastShortTermInputLoudness) ? shortTermAttackTC : shortTermReleaseTC; + var longTc = (longTermInputLoudness > lastLongTermInputLoudness) ? longTermAttackTC : longTermReleaseTC; + + shortTermInputLoudness += shortTc * (lastShortTermInputLoudness - shortTermInputLoudness); + longTermInputLoudness += longTc * (lastLongTermInputLoudness - longTermInputLoudness); + + lastShortTermInputLoudness = shortTermInputLoudness; + lastLongTermInputLoudness = longTermInputLoudness; + + if (shortTermInputLoudness > lastLongTermInputLoudness + activationThreshold) { holdCount = holdReset; } else { holdCount = Math.max(holdCount - 1, 0); From 6f400796214c1f26a15def2f98fe619806298649 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 21 Feb 2019 14:04:08 -0800 Subject: [PATCH 015/132] added a button to enable server loopback of audio --- interface/resources/qml/hifi/audio/Audio.qml | 7 ++ .../qml/hifi/audio/LoopbackAudio.qml | 75 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 interface/resources/qml/hifi/audio/LoopbackAudio.qml diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 34ae64aee8..e340ec5003 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -313,5 +313,12 @@ Rectangle { (bar.currentIndex === 0 && !isVR); anchors { left: parent.left; leftMargin: margins.paddings } } + LoopbackAudio { + x: margins.paddings + + visible: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR); + anchors { left: parent.left; leftMargin: margins.paddings } + } } } diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml new file mode 100644 index 0000000000..6d5f8d88fd --- /dev/null +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -0,0 +1,75 @@ +// +// LoopbackAudio.qml +// qml/hifi/audio +// +// Created by Seth Alves on 2019-2-18 +// Copyright 2019 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.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls + +RowLayout { + property bool audioLoopedBack: false; + function startAudioLoopback() { + if (!audioLoopedBack) { + audioLoopedBack = true; + AudioScope.setServerEcho(true); + } + } + function stopAudioLoopback () { + if (audioLoopedBack) { + audioLoopedBack = false; + AudioScope.setServerEcho(false); + } + } + + Component.onDestruction: stopAudioLoopback(); + onVisibleChanged: { + if (!visible) { + stopAudioLoopback(); + } + } + + HifiConstants { id: hifi; } + + Button { + id: control + background: Rectangle { + implicitWidth: 20; + implicitHeight: 20; + radius: hifi.buttons.radius; + gradient: Gradient { + GradientStop { + position: 0.2; + color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black]; + } + GradientStop { + position: 1.0; + color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black]; + } + } + } + contentItem: HiFiGlyphs { + size: 14; + color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "white"; + text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.playback_play; + } + + onClicked: audioLoopedBack ? stopAudioLoopback() : startAudioLoopback(); + } + + RalewayRegular { + Layout.leftMargin: 2; + size: 14; + color: "white"; + text: audioLoopedBack ? qsTr("Disable Audio Loopback") : qsTr("Enable Audio Loopback"); + } +} From 9ff99c721386cf620dc6dd26d35138f87b295d44 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 22 Feb 2019 09:33:23 -0800 Subject: [PATCH 016/132] make server-audio-loopback button work in HMDs --- interface/resources/qml/hifi/audio/LoopbackAudio.qml | 4 ++-- libraries/audio-client/src/AudioClient.h | 12 ++++++------ libraries/audio/src/AbstractAudioInterface.h | 9 ++++++++- .../script-engine/src/AudioScriptingInterface.cpp | 12 ++++++++++++ .../script-engine/src/AudioScriptingInterface.h | 12 ++++++++++++ 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml index 6d5f8d88fd..2f0dbe5950 100644 --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -21,13 +21,13 @@ RowLayout { function startAudioLoopback() { if (!audioLoopedBack) { audioLoopedBack = true; - AudioScope.setServerEcho(true); + AudioScriptingInterface.setServerEcho(true); } } function stopAudioLoopback () { if (audioLoopedBack) { audioLoopedBack = false; - AudioScope.setServerEcho(false); + AudioScriptingInterface.setServerEcho(false); } } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 6d3483b0f8..b9648219a5 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -213,13 +213,13 @@ public slots: void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true); bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; } - bool getLocalEcho() { return _shouldEchoLocally; } - void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; } - void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } + virtual bool getLocalEcho() override { return _shouldEchoLocally; } + virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; } + virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; } - bool getServerEcho() { return _shouldEchoToServer; } - void setServerEcho(bool serverEcho) { _shouldEchoToServer = serverEcho; } - void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; } + virtual bool getServerEcho() override { return _shouldEchoToServer; } + virtual void setServerEcho(bool serverEcho) override { _shouldEchoToServer = serverEcho; } + virtual void toggleServerEcho() override { _shouldEchoToServer = !_shouldEchoToServer; } void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); void sendMuteEnvironmentPacket(); diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 0f075ab224..e9e40e95f9 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -45,9 +45,16 @@ public slots: virtual bool shouldLoopbackInjectors() { return false; } virtual bool setIsStereoInput(bool stereo) = 0; - virtual bool isStereoInput() = 0; + virtual bool getLocalEcho() = 0; + virtual void setLocalEcho(bool localEcho) = 0; + virtual void toggleLocalEcho() = 0; + + virtual bool getServerEcho() = 0; + virtual void setServerEcho(bool serverEcho) = 0; + virtual void toggleServerEcho() = 0; + signals: void isStereoInputChanged(bool isStereo); }; diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 8e54d2d5de..b12b55c3f7 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -88,3 +88,15 @@ bool AudioScriptingInterface::isStereoInput() { } return stereoEnabled; } + +void AudioScriptingInterface::setServerEcho(bool serverEcho) { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho)); + } +} + +void AudioScriptingInterface::setLocalEcho(bool localEcho) { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho)); + } +} diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index d2f886d2dd..23cc02248d 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -66,6 +66,18 @@ public: _localAudioInterface->getAudioSolo().reset(); } + /**jsdoc + * @function Audio.setServerEcho + * @parm {boolean} serverEcho + */ + Q_INVOKABLE void setServerEcho(bool serverEcho); + + /**jsdoc + * @function Audio.setLocalEcho + * @parm {boolean} localEcho + */ + Q_INVOKABLE void setLocalEcho(bool localEcho); + protected: AudioScriptingInterface() = default; From 0523a0d06dc6fa751cfb7111c8f1730b96d548fa Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 23 Feb 2019 14:23:09 -0800 Subject: [PATCH 017/132] don't disable server echo when audio qml page is closed --- .../qml/hifi/audio/LoopbackAudio.qml | 9 +----- .../src/AudioScriptingInterface.cpp | 28 +++++++++++++++++++ .../src/AudioScriptingInterface.h | 21 ++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml index 2f0dbe5950..3ecf09c948 100644 --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -17,7 +17,7 @@ import stylesUit 1.0 import controlsUit 1.0 as HifiControls RowLayout { - property bool audioLoopedBack: false; + property bool audioLoopedBack: AudioScriptingInterface.getServerEcho(); function startAudioLoopback() { if (!audioLoopedBack) { audioLoopedBack = true; @@ -31,13 +31,6 @@ RowLayout { } } - Component.onDestruction: stopAudioLoopback(); - onVisibleChanged: { - if (!visible) { - stopAudioLoopback(); - } - } - HifiConstants { id: hifi; } Button { diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index b12b55c3f7..65d71e46e6 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -89,14 +89,42 @@ bool AudioScriptingInterface::isStereoInput() { return stereoEnabled; } +bool AudioScriptingInterface::getServerEcho() { + bool serverEchoEnabled = false; + if (_localAudioInterface) { + serverEchoEnabled = _localAudioInterface->getServerEcho(); + } + return serverEchoEnabled; +} + void AudioScriptingInterface::setServerEcho(bool serverEcho) { if (_localAudioInterface) { QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho)); } } +void AudioScriptingInterface::toggleServerEcho() { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "toggleServerEcho"); + } +} + +bool AudioScriptingInterface::getLocalEcho() { + bool localEchoEnabled = false; + if (_localAudioInterface) { + localEchoEnabled = _localAudioInterface->getLocalEcho(); + } + return localEchoEnabled; +} + void AudioScriptingInterface::setLocalEcho(bool localEcho) { if (_localAudioInterface) { QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho)); } } + +void AudioScriptingInterface::toggleLocalEcho() { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "toggleLocalEcho"); + } +} diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 23cc02248d..a6801dcdcb 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -66,18 +66,39 @@ public: _localAudioInterface->getAudioSolo().reset(); } + /**jsdoc + * @function Audio.getServerEcho + */ + Q_INVOKABLE bool getServerEcho(); + /**jsdoc * @function Audio.setServerEcho * @parm {boolean} serverEcho */ Q_INVOKABLE void setServerEcho(bool serverEcho); + /**jsdoc + * @function Audio.toggleServerEcho + */ + Q_INVOKABLE void toggleServerEcho(); + + /**jsdoc + * @function Audio.getLocalEcho + */ + Q_INVOKABLE bool getLocalEcho(); + /**jsdoc * @function Audio.setLocalEcho * @parm {boolean} localEcho */ Q_INVOKABLE void setLocalEcho(bool localEcho); + /**jsdoc + * @function Audio.toggleLocalEcho + */ + Q_INVOKABLE void toggleLocalEcho(); + + protected: AudioScriptingInterface() = default; From 90a6e0d9b01446c4f4f0aa28be66c7961b8d3a24 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 4 Mar 2019 11:42:43 -0800 Subject: [PATCH 018/132] changing mute warning position --- scripts/system/audioMuteOverlay.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 96f6d636dc..cd0c99ab6e 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -43,7 +43,7 @@ if (HMD.active) { warningOverlayID = Overlays.addOverlay("text3d", { name: "Muted-Warning", - localPosition: { x: 0, y: 0, z: -1.0 }, + localPosition: { x: 0.0, y: -0.5, z: -1.0 }, localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), text: warningText, textAlpha: 1, @@ -64,8 +64,8 @@ name: "Muted-Warning", font: { size: 36 }, text: warningText, - x: Window.innerWidth / 2 - textDimensions.x / 2, - y: Window.innerHeight / 2 - textDimensions.y / 2, + x: (Window.innerWidth - textDimensions.x) / 2, + y: (Window.innerHeight - textDimensions.y), width: textDimensions.x, height: textDimensions.y, textColor: { red: 226, green: 51, blue: 77 }, From 7860db68eb6ca4f557582fc022f42873b16b4fda Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 4 Mar 2019 13:33:16 -0800 Subject: [PATCH 019/132] culling hearing oneself and testing sound --- interface/resources/qml/hifi/audio/Audio.qml | 7 ---- libraries/audio-client/src/AudioClient.h | 12 +++--- libraries/audio/src/AbstractAudioInterface.h | 8 ---- .../src/AudioScriptingInterface.cpp | 42 +------------------ .../src/AudioScriptingInterface.h | 33 --------------- 5 files changed, 7 insertions(+), 95 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index e340ec5003..34ae64aee8 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -313,12 +313,5 @@ Rectangle { (bar.currentIndex === 0 && !isVR); anchors { left: parent.left; leftMargin: margins.paddings } } - LoopbackAudio { - x: margins.paddings - - visible: (bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR); - anchors { left: parent.left; leftMargin: margins.paddings } - } } } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index b9648219a5..6d3483b0f8 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -213,13 +213,13 @@ public slots: void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true); bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; } - virtual bool getLocalEcho() override { return _shouldEchoLocally; } - virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; } - virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; } + bool getLocalEcho() { return _shouldEchoLocally; } + void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; } + void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } - virtual bool getServerEcho() override { return _shouldEchoToServer; } - virtual void setServerEcho(bool serverEcho) override { _shouldEchoToServer = serverEcho; } - virtual void toggleServerEcho() override { _shouldEchoToServer = !_shouldEchoToServer; } + bool getServerEcho() { return _shouldEchoToServer; } + void setServerEcho(bool serverEcho) { _shouldEchoToServer = serverEcho; } + void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; } void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); void sendMuteEnvironmentPacket(); diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index e9e40e95f9..dc7d25fdc2 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -47,14 +47,6 @@ public slots: virtual bool setIsStereoInput(bool stereo) = 0; virtual bool isStereoInput() = 0; - virtual bool getLocalEcho() = 0; - virtual void setLocalEcho(bool localEcho) = 0; - virtual void toggleLocalEcho() = 0; - - virtual bool getServerEcho() = 0; - virtual void setServerEcho(bool serverEcho) = 0; - virtual void toggleServerEcho() = 0; - signals: void isStereoInputChanged(bool isStereo); }; diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 65d71e46e6..c695d67d91 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -87,44 +87,4 @@ bool AudioScriptingInterface::isStereoInput() { stereoEnabled = _localAudioInterface->isStereoInput(); } return stereoEnabled; -} - -bool AudioScriptingInterface::getServerEcho() { - bool serverEchoEnabled = false; - if (_localAudioInterface) { - serverEchoEnabled = _localAudioInterface->getServerEcho(); - } - return serverEchoEnabled; -} - -void AudioScriptingInterface::setServerEcho(bool serverEcho) { - if (_localAudioInterface) { - QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho)); - } -} - -void AudioScriptingInterface::toggleServerEcho() { - if (_localAudioInterface) { - QMetaObject::invokeMethod(_localAudioInterface, "toggleServerEcho"); - } -} - -bool AudioScriptingInterface::getLocalEcho() { - bool localEchoEnabled = false; - if (_localAudioInterface) { - localEchoEnabled = _localAudioInterface->getLocalEcho(); - } - return localEchoEnabled; -} - -void AudioScriptingInterface::setLocalEcho(bool localEcho) { - if (_localAudioInterface) { - QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho)); - } -} - -void AudioScriptingInterface::toggleLocalEcho() { - if (_localAudioInterface) { - QMetaObject::invokeMethod(_localAudioInterface, "toggleLocalEcho"); - } -} +} \ No newline at end of file diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index a6801dcdcb..d2f886d2dd 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -66,39 +66,6 @@ public: _localAudioInterface->getAudioSolo().reset(); } - /**jsdoc - * @function Audio.getServerEcho - */ - Q_INVOKABLE bool getServerEcho(); - - /**jsdoc - * @function Audio.setServerEcho - * @parm {boolean} serverEcho - */ - Q_INVOKABLE void setServerEcho(bool serverEcho); - - /**jsdoc - * @function Audio.toggleServerEcho - */ - Q_INVOKABLE void toggleServerEcho(); - - /**jsdoc - * @function Audio.getLocalEcho - */ - Q_INVOKABLE bool getLocalEcho(); - - /**jsdoc - * @function Audio.setLocalEcho - * @parm {boolean} localEcho - */ - Q_INVOKABLE void setLocalEcho(bool localEcho); - - /**jsdoc - * @function Audio.toggleLocalEcho - */ - Q_INVOKABLE void toggleLocalEcho(); - - protected: AudioScriptingInterface() = default; From 7780db813da4196f6fc4ce555172acacb70793ff Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Mon, 16 Apr 2018 15:26:06 +0300 Subject: [PATCH 020/132] Save the "Mute Microphone" setting --- interface/src/scripting/Audio.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..b4e3d7913b 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -25,6 +25,8 @@ QString Audio::DESKTOP { "Desktop" }; QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; +Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false }; + float Audio::loudnessToLevel(float loudness) { float level = loudness * (1/32768.0f); // level in [0, 1] @@ -42,6 +44,7 @@ Audio::Audio() : _devices(_contextIsHMD) { connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); onContextChanged(); + setMuted(mutedSetting.get()); } bool Audio::startRecording(const QString& filepath) { @@ -89,6 +92,15 @@ bool Audio::noiseReductionEnabled() const { }); } +void Audio::onMutedChanged() { + bool isMuted = DependencyManager::get()->isMuted(); + if (_isMuted != isMuted) { + _isMuted = isMuted; + mutedSetting.set(_isMuted); + emit mutedChanged(_isMuted); + } +} + void Audio::enableNoiseReduction(bool enable) { bool changed = false; withWriteLock([&] { From d54d0280c2469b2cec8ead405fe754ced5c4604f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 17 Feb 2019 14:21:23 -0800 Subject: [PATCH 021/132] rework audioMuteOverlay.js --- interface/src/scripting/Audio.cpp | 10 +-- scripts/defaultScripts.js | 3 +- scripts/system/audioMuteOverlay.js | 140 +++++++++++++---------------- 3 files changed, 63 insertions(+), 90 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index b4e3d7913b..bb40f69b0b 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -76,6 +76,7 @@ void Audio::setMuted(bool isMuted) { withWriteLock([&] { if (_isMuted != isMuted) { _isMuted = isMuted; + mutedSetting.set(_isMuted); auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); changed = true; @@ -92,15 +93,6 @@ bool Audio::noiseReductionEnabled() const { }); } -void Audio::onMutedChanged() { - bool isMuted = DependencyManager::get()->isMuted(); - if (_isMuted != isMuted) { - _isMuted = isMuted; - mutedSetting.set(_isMuted); - emit mutedChanged(_isMuted); - } -} - void Audio::enableNoiseReduction(bool enable) { bool changed = false; withWriteLock([&] { diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index bd7e79dffc..e392680df9 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/miniTablet.js" + "system/miniTablet.js", + "system/audioMuteOverlay.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 731d62017d..14ac96c8c6 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -1,104 +1,84 @@ -"use strict"; -/* jslint vars: true, plusplus: true, forin: true*/ -/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */ -/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // audioMuteOverlay.js // // client script that creates an overlay to provide mute feedback // // Created by Triplelexx on 17/03/09 +// Reworked by Seth Alves on 2019-2-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 // +"use strict"; + +/* global Audio, Script, Overlays, Quat, MyAvatar */ + (function() { // BEGIN LOCAL_SCOPE - var utilsPath = Script.resolvePath('../developer/libraries/utils.js'); - Script.include(utilsPath); - var TWEEN_SPEED = 0.025; - var MIX_AMOUNT = 0.25; + var lastInputLoudness = 0.0; + var sampleRate = 8.0; // Hz + var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)) // 500 milliseconds attack + var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)) // 1000 milliseconds release + var holdReset = 2.0 * sampleRate; // 2 seconds hold + var holdCount = 0; + var warningOverlayID = null; - var overlayPosition = Vec3.ZERO; - var tweenPosition = 0; - var startColor = { - red: 170, - green: 170, - blue: 170 - }; - var endColor = { - red: 255, - green: 0, - blue: 0 - }; - var overlayID; + function showWarning() { + if (warningOverlayID) { + return; + } + warningOverlayID = Overlays.addOverlay("text3d", { + name: "Muted-Warning", + localPosition: { x: 0.2, y: -0.35, z: -1.0 }, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), + text: "Warning: you are muted", + textAlpha: 1, + color: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + lineHeight: 0.042, + visible: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") + }); + }; - Script.update.connect(update); - Script.scriptEnding.connect(cleanup); + function hideWarning() { + if (!warningOverlayID) { + return; + } + Overlays.deleteOverlay(warningOverlayID); + warningOverlayID = null; + } - function update(dt) { - if (!Audio.muted) { - if (hasOverlay()) { - deleteOverlay(); - } - } else if (!hasOverlay()) { - createOverlay(); - } else { - updateOverlay(); - } - } + function cleanup() { + Overlays.deleteOverlay(warningOverlayID); + } - function getOffsetPosition() { - return Vec3.sum(Camera.position, Quat.getFront(Camera.orientation)); - } + Script.scriptEnding.connect(cleanup); - function createOverlay() { - overlayPosition = getOffsetPosition(); - overlayID = Overlays.addOverlay("sphere", { - position: overlayPosition, - rotation: Camera.orientation, - alpha: 0.9, - dimensions: 0.1, - solid: true, - ignoreRayIntersection: true - }); - } + Script.setInterval(function() { - function hasOverlay() { - return Overlays.getProperty(overlayID, "position") !== undefined; - } + var inputLoudness = Audio.inputLevel; + var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; + inputLoudness += tc * (lastInputLoudness - inputLoudness); + lastInputLoudness = inputLoudness; - function updateOverlay() { - // increase by TWEEN_SPEED until completion - if (tweenPosition < 1) { - tweenPosition += TWEEN_SPEED; - } else { - // after tween completion reset to zero and flip values to ping pong - tweenPosition = 0; - for (var component in startColor) { - var storedColor = startColor[component]; - startColor[component] = endColor[component]; - endColor[component] = storedColor; - } - } - // mix previous position with new and mix colors - overlayPosition = Vec3.mix(overlayPosition, getOffsetPosition(), MIX_AMOUNT); - Overlays.editOverlay(overlayID, { - color: colorMix(startColor, endColor, easeIn(tweenPosition)), - position: overlayPosition, - rotation: Camera.orientation - }); - } + if (Audio.muted && inputLoudness > 0.3) { + holdCount = holdReset; + } else { + holdCount = Math.max(holdCount - 1, 0); + } - function deleteOverlay() { - Overlays.deleteOverlay(overlayID); - } + if (holdCount > 0) { + showWarning(); + } else { + hideWarning(); + } + }, 1000.0 / sampleRate); - function cleanup() { - deleteOverlay(); - Audio.muted.disconnect(onMuteToggled); - Script.update.disconnect(update); - } }()); // END LOCAL_SCOPE From 3ecd86caee33cb72aaadbf2fc3fe90cde635a5c6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 19 Feb 2019 09:32:41 -0800 Subject: [PATCH 022/132] add a way to disble muted warning from audio panel. fix positioning of warning. hide warning when removing timer. --- interface/resources/qml/hifi/audio/Audio.qml | 13 ++ interface/src/scripting/Audio.cpp | 25 ++++ interface/src/scripting/Audio.h | 14 +- libraries/audio-client/src/AudioClient.cpp | 8 + libraries/audio-client/src/AudioClient.h | 5 + scripts/system/audioMuteOverlay.js | 146 ++++++++++++------- 6 files changed, 155 insertions(+), 56 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..34ae64aee8 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -159,6 +159,19 @@ Rectangle { onXChanged: rightMostInputLevelPos = x + width } } + + RowLayout { + spacing: muteMic.spacing*2; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Warn when muted"); + checked: AudioScriptingInterface.warnWhenMuted; + onClicked: { + AudioScriptingInterface.warnWhenMuted = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding + } + } + } } Separator {} diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index bb40f69b0b..4a4b3c146b 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -25,6 +25,7 @@ QString Audio::DESKTOP { "Desktop" }; QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; +Setting::Handle enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true }; Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false }; @@ -39,10 +40,12 @@ Audio::Audio() : _devices(_contextIsHMD) { auto client = DependencyManager::get().data(); connect(client, &AudioClient::muteToggled, this, &Audio::setMuted); connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction); + connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted); connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); + enableWarnWhenMuted(enableWarnWhenMutedSetting.get()); onContextChanged(); setMuted(mutedSetting.get()); } @@ -109,6 +112,28 @@ void Audio::enableNoiseReduction(bool enable) { } } +bool Audio::warnWhenMutedEnabled() const { + return resultWithReadLock([&] { + return _enableWarnWhenMuted; + }); +} + +void Audio::enableWarnWhenMuted(bool enable) { + bool changed = false; + withWriteLock([&] { + if (_enableWarnWhenMuted != enable) { + _enableWarnWhenMuted = enable; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setWarnWhenMuted", Q_ARG(bool, enable), Q_ARG(bool, false)); + enableWarnWhenMutedSetting.set(enable); + changed = true; + } + }); + if (changed) { + emit warnWhenMutedChanged(enable); + } +} + float Audio::getInputVolume() const { return resultWithReadLock([&] { return _inputVolume; diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..7e216eb0b2 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -58,6 +58,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) + Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged) Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) @@ -75,6 +76,7 @@ public: bool isMuted() const; bool noiseReductionEnabled() const; + bool warnWhenMutedEnabled() const; float getInputVolume() const; float getInputLevel() const; bool isClipping() const; @@ -192,7 +194,7 @@ signals: * }); */ void mutedChanged(bool isMuted); - + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -201,6 +203,14 @@ signals: */ void noiseReductionChanged(bool isEnabled); + /**jsdoc + * Triggered when "warn when muted" is enabled or disabled. + * @function Audio.warnWhenMutedChanged + * @param {boolean} isEnabled - true if "warn when muted" is enabled, otherwise false. + * @returns {Signal} + */ + void warnWhenMutedChanged(bool isEnabled); + /**jsdoc * Triggered when the input audio volume changes. * @function Audio.inputVolumeChanged @@ -248,6 +258,7 @@ public slots: private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); + void enableWarnWhenMuted(bool enable); void setInputVolume(float volume); void onInputLoudnessChanged(float loudness, bool isClipping); @@ -262,6 +273,7 @@ private: bool _isClipping { false }; bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. + bool _enableWarnWhenMuted { true }; bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 8c50a195ee..1c10d24f23 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1531,6 +1531,14 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) { } } +void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) { + if (_warnWhenMuted != enable) { + _warnWhenMuted = enable; + if (emitSignal) { + emit warnWhenMutedChanged(_warnWhenMuted); + } + } +} bool AudioClient::setIsStereoInput(bool isStereoInput) { bool stereoInputChanged = false; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 29036b7c71..6d3483b0f8 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -210,6 +210,9 @@ public slots: void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true); bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } + void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true); + bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; } + bool getLocalEcho() { return _shouldEchoLocally; } void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; } void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } @@ -246,6 +249,7 @@ signals: void inputVolumeChanged(float volume); void muteToggled(bool muted); void noiseReductionChanged(bool noiseReductionEnabled); + void warnWhenMutedChanged(bool warnWhenMutedEnabled); void mutedByMixer(); void inputReceived(const QByteArray& inputSamples); void inputLoudnessChanged(float loudness, bool isClipping); @@ -365,6 +369,7 @@ private: bool _shouldEchoLocally; bool _shouldEchoToServer; bool _isNoiseGateEnabled; + bool _warnWhenMuted; bool _reverb; AudioEffectOptions _scriptReverbOptions; diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 14ac96c8c6..d759b7d885 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -17,68 +17,104 @@ (function() { // BEGIN LOCAL_SCOPE - var lastInputLoudness = 0.0; - var sampleRate = 8.0; // Hz - var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)) // 500 milliseconds attack - var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)) // 1000 milliseconds release - var holdReset = 2.0 * sampleRate; // 2 seconds hold - var holdCount = 0; - var warningOverlayID = null; + var lastInputLoudness = 0.0; + var sampleRate = 8.0; // Hz + var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack + var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + var holdReset = 2.0 * sampleRate; // 2 seconds hold + var holdCount = 0; + var warningOverlayID = null; + var pollInterval = null; + var warningText = "Muted"; + var textDimensions = { x: 100, y: 50 }; - function showWarning() { - if (warningOverlayID) { - return; - } - warningOverlayID = Overlays.addOverlay("text3d", { - name: "Muted-Warning", - localPosition: { x: 0.2, y: -0.35, z: -1.0 }, - localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), - text: "Warning: you are muted", - textAlpha: 1, - color: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - lineHeight: 0.042, - visible: true, - ignoreRayIntersection: true, - drawInFront: true, - grabbable: false, - parentID: MyAvatar.SELF_ID, - parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") - }); - }; + function showWarning() { + if (warningOverlayID) { + return; + } - function hideWarning() { - if (!warningOverlayID) { - return; - } - Overlays.deleteOverlay(warningOverlayID); - warningOverlayID = null; - } + var windowWidth; + var windowHeight; + if (HMD.active) { + var viewportDimension = Controller.getViewportDimensions(); + windowWidth = viewportDimension.x; + windowHeight = viewportDimension.y; + } else { + windowWidth = Window.innerWidth; + windowHeight = Window.innerHeight; + } - function cleanup() { - Overlays.deleteOverlay(warningOverlayID); - } + warningOverlayID = Overlays.addOverlay("text", { + name: "Muted-Warning", + font: { size: 36 }, + text: warningText, + x: windowWidth / 2 - textDimensions.x / 2, + y: windowHeight / 2 - textDimensions.y / 2, + width: textDimensions.x, + height: textDimensions.y, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + visible: true + }); + } - Script.scriptEnding.connect(cleanup); + function hideWarning() { + if (!warningOverlayID) { + return; + } + Overlays.deleteOverlay(warningOverlayID); + warningOverlayID = null; + } - Script.setInterval(function() { + function startPoll() { + if (pollInterval) { + return; + } + pollInterval = Script.setInterval(function() { + var inputLoudness = Audio.inputLevel; + var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; + inputLoudness += tc * (lastInputLoudness - inputLoudness); + lastInputLoudness = inputLoudness; - var inputLoudness = Audio.inputLevel; - var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; - inputLoudness += tc * (lastInputLoudness - inputLoudness); - lastInputLoudness = inputLoudness; + if (inputLoudness > 0.1) { + holdCount = holdReset; + } else { + holdCount = Math.max(holdCount - 1, 0); + } - if (Audio.muted && inputLoudness > 0.3) { - holdCount = holdReset; - } else { - holdCount = Math.max(holdCount - 1, 0); - } + if (holdCount > 0) { + showWarning(); + } else { + hideWarning(); + } + }, 1000.0 / sampleRate); + } - if (holdCount > 0) { - showWarning(); - } else { - hideWarning(); - } - }, 1000.0 / sampleRate); + function stopPoll() { + if (!pollInterval) { + return; + } + Script.clearInterval(pollInterval); + pollInterval = null; + hideWarning(); + } + + function startOrStopPoll() { + if (Audio.warnWhenMuted && Audio.muted) { + startPoll(); + } else { + stopPoll(); + } + } + + function cleanup() { + stopPoll(); + } + + Script.scriptEnding.connect(cleanup); + + startOrStopPoll(); + Audio.mutedChanged.connect(startOrStopPoll); + Audio.warnWhenMutedChanged.connect(startOrStopPoll); }()); // END LOCAL_SCOPE From f83f2b8226b17f2c7446534cd155b917d01a0cc1 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 20 Feb 2019 12:53:43 -0800 Subject: [PATCH 023/132] keep muted warning in center of view for HMD --- scripts/system/audioMuteOverlay.js | 51 ++++++++++++++++++------------ 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index d759b7d885..65793d1d87 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -26,36 +26,45 @@ var warningOverlayID = null; var pollInterval = null; var warningText = "Muted"; - var textDimensions = { x: 100, y: 50 }; function showWarning() { if (warningOverlayID) { return; } - var windowWidth; - var windowHeight; if (HMD.active) { - var viewportDimension = Controller.getViewportDimensions(); - windowWidth = viewportDimension.x; - windowHeight = viewportDimension.y; + warningOverlayID = Overlays.addOverlay("text3d", { + name: "Muted-Warning", + localPosition: { x: 0, y: 0, z: -1.0 }, + localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), + text: warningText, + textAlpha: 1, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + lineHeight: 0.042, + dimensions: { x: 0.11, y: 0.05 }, + visible: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + parentID: MyAvatar.SELF_ID, + parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") + }); } else { - windowWidth = Window.innerWidth; - windowHeight = Window.innerHeight; + var textDimensions = { x: 100, y: 50 }; + warningOverlayID = Overlays.addOverlay("text", { + name: "Muted-Warning", + font: { size: 36 }, + text: warningText, + x: Window.innerWidth / 2 - textDimensions.x / 2, + y: Window.innerHeight / 2 - textDimensions.y / 2, + width: textDimensions.x, + height: textDimensions.y, + textColor: { red: 226, green: 51, blue: 77 }, + backgroundAlpha: 0, + visible: true + }); } - - warningOverlayID = Overlays.addOverlay("text", { - name: "Muted-Warning", - font: { size: 36 }, - text: warningText, - x: windowWidth / 2 - textDimensions.x / 2, - y: windowHeight / 2 - textDimensions.y / 2, - width: textDimensions.x, - height: textDimensions.y, - textColor: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - visible: true - }); } function hideWarning() { From 216b53dcb0975aba559e410393244ce44b0cc691 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 20 Feb 2019 13:07:24 -0800 Subject: [PATCH 024/132] attempt to take background noise into account when triggering mute warning --- scripts/system/audioMuteOverlay.js | 33 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 65793d1d87..96f6d636dc 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -13,14 +13,22 @@ "use strict"; -/* global Audio, Script, Overlays, Quat, MyAvatar */ +/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */ (function() { // BEGIN LOCAL_SCOPE - var lastInputLoudness = 0.0; + var lastShortTermInputLoudness = 0.0; + var lastLongTermInputLoudness = 0.0; var sampleRate = 8.0; // Hz - var attackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack - var releaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + + var shortTermAttackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack + var shortTermReleaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release + + var longTermAttackTC = Math.exp(-1.0 / (sampleRate * 5.0)); // 5 second attack + var longTermReleaseTC = Math.exp(-1.0 / (sampleRate * 10.0)); // 10 seconds release + + var activationThreshold = 0.05; // how much louder short-term needs to be than long-term to trigger warning + var holdReset = 2.0 * sampleRate; // 2 seconds hold var holdCount = 0; var warningOverlayID = null; @@ -80,12 +88,19 @@ return; } pollInterval = Script.setInterval(function() { - var inputLoudness = Audio.inputLevel; - var tc = (inputLoudness > lastInputLoudness) ? attackTC : releaseTC; - inputLoudness += tc * (lastInputLoudness - inputLoudness); - lastInputLoudness = inputLoudness; + var shortTermInputLoudness = Audio.inputLevel; + var longTermInputLoudness = shortTermInputLoudness; - if (inputLoudness > 0.1) { + var shortTc = (shortTermInputLoudness > lastShortTermInputLoudness) ? shortTermAttackTC : shortTermReleaseTC; + var longTc = (longTermInputLoudness > lastLongTermInputLoudness) ? longTermAttackTC : longTermReleaseTC; + + shortTermInputLoudness += shortTc * (lastShortTermInputLoudness - shortTermInputLoudness); + longTermInputLoudness += longTc * (lastLongTermInputLoudness - longTermInputLoudness); + + lastShortTermInputLoudness = shortTermInputLoudness; + lastLongTermInputLoudness = longTermInputLoudness; + + if (shortTermInputLoudness > lastLongTermInputLoudness + activationThreshold) { holdCount = holdReset; } else { holdCount = Math.max(holdCount - 1, 0); From 6ee236e783e8a1faf2075b0cf462f11770f6a486 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 21 Feb 2019 14:04:08 -0800 Subject: [PATCH 025/132] added a button to enable server loopback of audio --- interface/resources/qml/hifi/audio/Audio.qml | 7 ++ .../qml/hifi/audio/LoopbackAudio.qml | 75 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 interface/resources/qml/hifi/audio/LoopbackAudio.qml diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 34ae64aee8..e340ec5003 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -313,5 +313,12 @@ Rectangle { (bar.currentIndex === 0 && !isVR); anchors { left: parent.left; leftMargin: margins.paddings } } + LoopbackAudio { + x: margins.paddings + + visible: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR); + anchors { left: parent.left; leftMargin: margins.paddings } + } } } diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml new file mode 100644 index 0000000000..6d5f8d88fd --- /dev/null +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -0,0 +1,75 @@ +// +// LoopbackAudio.qml +// qml/hifi/audio +// +// Created by Seth Alves on 2019-2-18 +// Copyright 2019 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.7 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls + +RowLayout { + property bool audioLoopedBack: false; + function startAudioLoopback() { + if (!audioLoopedBack) { + audioLoopedBack = true; + AudioScope.setServerEcho(true); + } + } + function stopAudioLoopback () { + if (audioLoopedBack) { + audioLoopedBack = false; + AudioScope.setServerEcho(false); + } + } + + Component.onDestruction: stopAudioLoopback(); + onVisibleChanged: { + if (!visible) { + stopAudioLoopback(); + } + } + + HifiConstants { id: hifi; } + + Button { + id: control + background: Rectangle { + implicitWidth: 20; + implicitHeight: 20; + radius: hifi.buttons.radius; + gradient: Gradient { + GradientStop { + position: 0.2; + color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black]; + } + GradientStop { + position: 1.0; + color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black]; + } + } + } + contentItem: HiFiGlyphs { + size: 14; + color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "white"; + text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.playback_play; + } + + onClicked: audioLoopedBack ? stopAudioLoopback() : startAudioLoopback(); + } + + RalewayRegular { + Layout.leftMargin: 2; + size: 14; + color: "white"; + text: audioLoopedBack ? qsTr("Disable Audio Loopback") : qsTr("Enable Audio Loopback"); + } +} From d5e8cba1eec07a12c6cf6a6f8e1919a015b541fb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 22 Feb 2019 09:33:23 -0800 Subject: [PATCH 026/132] make server-audio-loopback button work in HMDs --- interface/resources/qml/hifi/audio/LoopbackAudio.qml | 4 ++-- libraries/audio-client/src/AudioClient.h | 12 ++++++------ libraries/audio/src/AbstractAudioInterface.h | 9 ++++++++- .../script-engine/src/AudioScriptingInterface.cpp | 12 ++++++++++++ .../script-engine/src/AudioScriptingInterface.h | 12 ++++++++++++ 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml index 6d5f8d88fd..2f0dbe5950 100644 --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -21,13 +21,13 @@ RowLayout { function startAudioLoopback() { if (!audioLoopedBack) { audioLoopedBack = true; - AudioScope.setServerEcho(true); + AudioScriptingInterface.setServerEcho(true); } } function stopAudioLoopback () { if (audioLoopedBack) { audioLoopedBack = false; - AudioScope.setServerEcho(false); + AudioScriptingInterface.setServerEcho(false); } } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 6d3483b0f8..b9648219a5 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -213,13 +213,13 @@ public slots: void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true); bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; } - bool getLocalEcho() { return _shouldEchoLocally; } - void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; } - void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } + virtual bool getLocalEcho() override { return _shouldEchoLocally; } + virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; } + virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; } - bool getServerEcho() { return _shouldEchoToServer; } - void setServerEcho(bool serverEcho) { _shouldEchoToServer = serverEcho; } - void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; } + virtual bool getServerEcho() override { return _shouldEchoToServer; } + virtual void setServerEcho(bool serverEcho) override { _shouldEchoToServer = serverEcho; } + virtual void toggleServerEcho() override { _shouldEchoToServer = !_shouldEchoToServer; } void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); void sendMuteEnvironmentPacket(); diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 0f075ab224..e9e40e95f9 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -45,9 +45,16 @@ public slots: virtual bool shouldLoopbackInjectors() { return false; } virtual bool setIsStereoInput(bool stereo) = 0; - virtual bool isStereoInput() = 0; + virtual bool getLocalEcho() = 0; + virtual void setLocalEcho(bool localEcho) = 0; + virtual void toggleLocalEcho() = 0; + + virtual bool getServerEcho() = 0; + virtual void setServerEcho(bool serverEcho) = 0; + virtual void toggleServerEcho() = 0; + signals: void isStereoInputChanged(bool isStereo); }; diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index 8e54d2d5de..b12b55c3f7 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -88,3 +88,15 @@ bool AudioScriptingInterface::isStereoInput() { } return stereoEnabled; } + +void AudioScriptingInterface::setServerEcho(bool serverEcho) { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho)); + } +} + +void AudioScriptingInterface::setLocalEcho(bool localEcho) { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho)); + } +} diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index d2f886d2dd..23cc02248d 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -66,6 +66,18 @@ public: _localAudioInterface->getAudioSolo().reset(); } + /**jsdoc + * @function Audio.setServerEcho + * @parm {boolean} serverEcho + */ + Q_INVOKABLE void setServerEcho(bool serverEcho); + + /**jsdoc + * @function Audio.setLocalEcho + * @parm {boolean} localEcho + */ + Q_INVOKABLE void setLocalEcho(bool localEcho); + protected: AudioScriptingInterface() = default; From 96142160066eea2064789078457015d193390536 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 23 Feb 2019 14:23:09 -0800 Subject: [PATCH 027/132] don't disable server echo when audio qml page is closed --- .../qml/hifi/audio/LoopbackAudio.qml | 9 +----- .../src/AudioScriptingInterface.cpp | 28 +++++++++++++++++++ .../src/AudioScriptingInterface.h | 21 ++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml index 2f0dbe5950..3ecf09c948 100644 --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -17,7 +17,7 @@ import stylesUit 1.0 import controlsUit 1.0 as HifiControls RowLayout { - property bool audioLoopedBack: false; + property bool audioLoopedBack: AudioScriptingInterface.getServerEcho(); function startAudioLoopback() { if (!audioLoopedBack) { audioLoopedBack = true; @@ -31,13 +31,6 @@ RowLayout { } } - Component.onDestruction: stopAudioLoopback(); - onVisibleChanged: { - if (!visible) { - stopAudioLoopback(); - } - } - HifiConstants { id: hifi; } Button { diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index b12b55c3f7..65d71e46e6 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -89,14 +89,42 @@ bool AudioScriptingInterface::isStereoInput() { return stereoEnabled; } +bool AudioScriptingInterface::getServerEcho() { + bool serverEchoEnabled = false; + if (_localAudioInterface) { + serverEchoEnabled = _localAudioInterface->getServerEcho(); + } + return serverEchoEnabled; +} + void AudioScriptingInterface::setServerEcho(bool serverEcho) { if (_localAudioInterface) { QMetaObject::invokeMethod(_localAudioInterface, "setServerEcho", Q_ARG(bool, serverEcho)); } } +void AudioScriptingInterface::toggleServerEcho() { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "toggleServerEcho"); + } +} + +bool AudioScriptingInterface::getLocalEcho() { + bool localEchoEnabled = false; + if (_localAudioInterface) { + localEchoEnabled = _localAudioInterface->getLocalEcho(); + } + return localEchoEnabled; +} + void AudioScriptingInterface::setLocalEcho(bool localEcho) { if (_localAudioInterface) { QMetaObject::invokeMethod(_localAudioInterface, "setLocalEcho", Q_ARG(bool, localEcho)); } } + +void AudioScriptingInterface::toggleLocalEcho() { + if (_localAudioInterface) { + QMetaObject::invokeMethod(_localAudioInterface, "toggleLocalEcho"); + } +} diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 23cc02248d..a6801dcdcb 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -66,18 +66,39 @@ public: _localAudioInterface->getAudioSolo().reset(); } + /**jsdoc + * @function Audio.getServerEcho + */ + Q_INVOKABLE bool getServerEcho(); + /**jsdoc * @function Audio.setServerEcho * @parm {boolean} serverEcho */ Q_INVOKABLE void setServerEcho(bool serverEcho); + /**jsdoc + * @function Audio.toggleServerEcho + */ + Q_INVOKABLE void toggleServerEcho(); + + /**jsdoc + * @function Audio.getLocalEcho + */ + Q_INVOKABLE bool getLocalEcho(); + /**jsdoc * @function Audio.setLocalEcho * @parm {boolean} localEcho */ Q_INVOKABLE void setLocalEcho(bool localEcho); + /**jsdoc + * @function Audio.toggleLocalEcho + */ + Q_INVOKABLE void toggleLocalEcho(); + + protected: AudioScriptingInterface() = default; From 4187104b1788955f050a20304bb1ccce737fd370 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 4 Mar 2019 14:12:09 -0800 Subject: [PATCH 028/132] culling mute state --- interface/resources/qml/hifi/audio/Audio.qml | 13 -- interface/src/scripting/Audio.cpp | 29 --- interface/src/scripting/Audio.h | 12 -- libraries/audio-client/src/AudioClient.cpp | 9 - libraries/audio-client/src/AudioClient.h | 5 - scripts/system/audioMuteOverlay.js | 202 ++++++++----------- 6 files changed, 81 insertions(+), 189 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index e340ec5003..aa64af22a3 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -159,19 +159,6 @@ Rectangle { onXChanged: rightMostInputLevelPos = x + width } } - - RowLayout { - spacing: muteMic.spacing*2; - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Warn when muted"); - checked: AudioScriptingInterface.warnWhenMuted; - onClicked: { - AudioScriptingInterface.warnWhenMuted = checked; - checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding - } - } - } } Separator {} diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 4a4b3c146b..2c4c29ff65 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -25,9 +25,6 @@ QString Audio::DESKTOP { "Desktop" }; QString Audio::HMD { "VR" }; Setting::Handle enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true }; -Setting::Handle enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true }; -Setting::Handle mutedSetting { QStringList{ Audio::AUDIO, "MuteMicrophone" }, false }; - float Audio::loudnessToLevel(float loudness) { float level = loudness * (1/32768.0f); // level in [0, 1] @@ -40,14 +37,11 @@ Audio::Audio() : _devices(_contextIsHMD) { auto client = DependencyManager::get().data(); connect(client, &AudioClient::muteToggled, this, &Audio::setMuted); connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction); - connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted); connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); - enableWarnWhenMuted(enableWarnWhenMutedSetting.get()); onContextChanged(); - setMuted(mutedSetting.get()); } bool Audio::startRecording(const QString& filepath) { @@ -79,7 +73,6 @@ void Audio::setMuted(bool isMuted) { withWriteLock([&] { if (_isMuted != isMuted) { _isMuted = isMuted; - mutedSetting.set(_isMuted); auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); changed = true; @@ -112,28 +105,6 @@ void Audio::enableNoiseReduction(bool enable) { } } -bool Audio::warnWhenMutedEnabled() const { - return resultWithReadLock([&] { - return _enableWarnWhenMuted; - }); -} - -void Audio::enableWarnWhenMuted(bool enable) { - bool changed = false; - withWriteLock([&] { - if (_enableWarnWhenMuted != enable) { - _enableWarnWhenMuted = enable; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setWarnWhenMuted", Q_ARG(bool, enable), Q_ARG(bool, false)); - enableWarnWhenMutedSetting.set(enable); - changed = true; - } - }); - if (changed) { - emit warnWhenMutedChanged(enable); - } -} - float Audio::getInputVolume() const { return resultWithReadLock([&] { return _inputVolume; diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 7e216eb0b2..fcf3c181da 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -58,7 +58,6 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) - Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged) Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) @@ -76,7 +75,6 @@ public: bool isMuted() const; bool noiseReductionEnabled() const; - bool warnWhenMutedEnabled() const; float getInputVolume() const; float getInputLevel() const; bool isClipping() const; @@ -203,14 +201,6 @@ signals: */ void noiseReductionChanged(bool isEnabled); - /**jsdoc - * Triggered when "warn when muted" is enabled or disabled. - * @function Audio.warnWhenMutedChanged - * @param {boolean} isEnabled - true if "warn when muted" is enabled, otherwise false. - * @returns {Signal} - */ - void warnWhenMutedChanged(bool isEnabled); - /**jsdoc * Triggered when the input audio volume changes. * @function Audio.inputVolumeChanged @@ -258,7 +248,6 @@ public slots: private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); - void enableWarnWhenMuted(bool enable); void setInputVolume(float volume); void onInputLoudnessChanged(float loudness, bool isClipping); @@ -273,7 +262,6 @@ private: bool _isClipping { false }; bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. - bool _enableWarnWhenMuted { true }; bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 1c10d24f23..b2e6167ffa 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1531,15 +1531,6 @@ void AudioClient::setNoiseReduction(bool enable, bool emitSignal) { } } -void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) { - if (_warnWhenMuted != enable) { - _warnWhenMuted = enable; - if (emitSignal) { - emit warnWhenMutedChanged(_warnWhenMuted); - } - } -} - bool AudioClient::setIsStereoInput(bool isStereoInput) { bool stereoInputChanged = false; if (isStereoInput != _isStereoInput && _inputDeviceInfo.supportedChannelCounts().contains(2)) { diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index b9648219a5..87e0f68e72 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -210,9 +210,6 @@ public slots: void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true); bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } - void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true); - bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; } - virtual bool getLocalEcho() override { return _shouldEchoLocally; } virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; } virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; } @@ -249,7 +246,6 @@ signals: void inputVolumeChanged(float volume); void muteToggled(bool muted); void noiseReductionChanged(bool noiseReductionEnabled); - void warnWhenMutedChanged(bool warnWhenMutedEnabled); void mutedByMixer(); void inputReceived(const QByteArray& inputSamples); void inputLoudnessChanged(float loudness, bool isClipping); @@ -369,7 +365,6 @@ private: bool _shouldEchoLocally; bool _shouldEchoToServer; bool _isNoiseGateEnabled; - bool _warnWhenMuted; bool _reverb; AudioEffectOptions _scriptReverbOptions; diff --git a/scripts/system/audioMuteOverlay.js b/scripts/system/audioMuteOverlay.js index 96f6d636dc..c597f75bca 100644 --- a/scripts/system/audioMuteOverlay.js +++ b/scripts/system/audioMuteOverlay.js @@ -1,144 +1,104 @@ +"use strict"; +/* jslint vars: true, plusplus: true, forin: true*/ +/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ // // audioMuteOverlay.js // // client script that creates an overlay to provide mute feedback // // Created by Triplelexx on 17/03/09 -// Reworked by Seth Alves on 2019-2-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 // -"use strict"; +(function () { // BEGIN LOCAL_SCOPE + var utilsPath = Script.resolvePath('../developer/libraries/utils.js'); + Script.include(utilsPath); -/* global Audio, Script, Overlays, Quat, MyAvatar, HMD */ + var TWEEN_SPEED = 0.025; + var MIX_AMOUNT = 0.25; -(function() { // BEGIN LOCAL_SCOPE + var overlayPosition = Vec3.ZERO; + var tweenPosition = 0; + var startColor = { + red: 170, + green: 170, + blue: 170 + }; + var endColor = { + red: 255, + green: 0, + blue: 0 + }; + var overlayID; - var lastShortTermInputLoudness = 0.0; - var lastLongTermInputLoudness = 0.0; - var sampleRate = 8.0; // Hz + Script.update.connect(update); + Script.scriptEnding.connect(cleanup); - var shortTermAttackTC = Math.exp(-1.0 / (sampleRate * 0.500)); // 500 milliseconds attack - var shortTermReleaseTC = Math.exp(-1.0 / (sampleRate * 1.000)); // 1000 milliseconds release - - var longTermAttackTC = Math.exp(-1.0 / (sampleRate * 5.0)); // 5 second attack - var longTermReleaseTC = Math.exp(-1.0 / (sampleRate * 10.0)); // 10 seconds release - - var activationThreshold = 0.05; // how much louder short-term needs to be than long-term to trigger warning - - var holdReset = 2.0 * sampleRate; // 2 seconds hold - var holdCount = 0; - var warningOverlayID = null; - var pollInterval = null; - var warningText = "Muted"; - - function showWarning() { - if (warningOverlayID) { - return; - } - - if (HMD.active) { - warningOverlayID = Overlays.addOverlay("text3d", { - name: "Muted-Warning", - localPosition: { x: 0, y: 0, z: -1.0 }, - localOrientation: Quat.fromVec3Degrees({ x: 0.0, y: 0.0, z: 0.0, w: 1.0 }), - text: warningText, - textAlpha: 1, - textColor: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - lineHeight: 0.042, - dimensions: { x: 0.11, y: 0.05 }, - visible: true, - ignoreRayIntersection: true, - drawInFront: true, - grabbable: false, - parentID: MyAvatar.SELF_ID, - parentJointIndex: MyAvatar.getJointIndex("_CAMERA_MATRIX") - }); - } else { - var textDimensions = { x: 100, y: 50 }; - warningOverlayID = Overlays.addOverlay("text", { - name: "Muted-Warning", - font: { size: 36 }, - text: warningText, - x: Window.innerWidth / 2 - textDimensions.x / 2, - y: Window.innerHeight / 2 - textDimensions.y / 2, - width: textDimensions.x, - height: textDimensions.y, - textColor: { red: 226, green: 51, blue: 77 }, - backgroundAlpha: 0, - visible: true - }); - } - } - - function hideWarning() { - if (!warningOverlayID) { - return; - } - Overlays.deleteOverlay(warningOverlayID); - warningOverlayID = null; - } - - function startPoll() { - if (pollInterval) { - return; - } - pollInterval = Script.setInterval(function() { - var shortTermInputLoudness = Audio.inputLevel; - var longTermInputLoudness = shortTermInputLoudness; - - var shortTc = (shortTermInputLoudness > lastShortTermInputLoudness) ? shortTermAttackTC : shortTermReleaseTC; - var longTc = (longTermInputLoudness > lastLongTermInputLoudness) ? longTermAttackTC : longTermReleaseTC; - - shortTermInputLoudness += shortTc * (lastShortTermInputLoudness - shortTermInputLoudness); - longTermInputLoudness += longTc * (lastLongTermInputLoudness - longTermInputLoudness); - - lastShortTermInputLoudness = shortTermInputLoudness; - lastLongTermInputLoudness = longTermInputLoudness; - - if (shortTermInputLoudness > lastLongTermInputLoudness + activationThreshold) { - holdCount = holdReset; - } else { - holdCount = Math.max(holdCount - 1, 0); + function update(dt) { + if (!Audio.muted) { + if (hasOverlay()) { + deleteOverlay(); } - - if (holdCount > 0) { - showWarning(); - } else { - hideWarning(); - } - }, 1000.0 / sampleRate); - } - - function stopPoll() { - if (!pollInterval) { - return; - } - Script.clearInterval(pollInterval); - pollInterval = null; - hideWarning(); - } - - function startOrStopPoll() { - if (Audio.warnWhenMuted && Audio.muted) { - startPoll(); + } else if (!hasOverlay()) { + createOverlay(); } else { - stopPoll(); + updateOverlay(); } } + function getOffsetPosition() { + return Vec3.sum(Camera.position, Quat.getFront(Camera.orientation)); + } + + function createOverlay() { + overlayPosition = getOffsetPosition(); + overlayID = Overlays.addOverlay("sphere", { + position: overlayPosition, + rotation: Camera.orientation, + alpha: 0.9, + dimensions: 0.1, + solid: true, + ignoreRayIntersection: true + }); + } + + function hasOverlay() { + return Overlays.getProperty(overlayID, "position") !== undefined; + } + + function updateOverlay() { + // increase by TWEEN_SPEED until completion + if (tweenPosition < 1) { + tweenPosition += TWEEN_SPEED; + } else { + // after tween completion reset to zero and flip values to ping pong + tweenPosition = 0; + for (var component in startColor) { + var storedColor = startColor[component]; + startColor[component] = endColor[component]; + endColor[component] = storedColor; + } + } + // mix previous position with new and mix colors + overlayPosition = Vec3.mix(overlayPosition, getOffsetPosition(), MIX_AMOUNT); + Overlays.editOverlay(overlayID, { + color: colorMix(startColor, endColor, easeIn(tweenPosition)), + position: overlayPosition, + rotation: Camera.orientation + }); + } + + function deleteOverlay() { + Overlays.deleteOverlay(overlayID); + } + function cleanup() { - stopPoll(); + deleteOverlay(); + Audio.muted.disconnect(onMuteToggled); + Script.update.disconnect(update); } - - Script.scriptEnding.connect(cleanup); - - startOrStopPoll(); - Audio.mutedChanged.connect(startOrStopPoll); - Audio.warnWhenMutedChanged.connect(startOrStopPoll); - -}()); // END LOCAL_SCOPE +}()); // END LOCAL_SCOPE \ No newline at end of file From 19ac8cae316d620427a8e1847d964526e526548a Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 4 Mar 2019 14:18:10 -0800 Subject: [PATCH 029/132] culling mute state --- scripts/defaultScripts.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index e392680df9..bd7e79dffc 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,8 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/emote.js", - "system/miniTablet.js", - "system/audioMuteOverlay.js" + "system/miniTablet.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js", From a68aaaa7a46cf8ab6a4bf090a50bd3ecccc20c48 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 4 Mar 2019 16:06:50 -0800 Subject: [PATCH 030/132] adding timer and ui color changes --- interface/resources/qml/hifi/audio/Audio.qml | 33 ++++++++----------- .../qml/hifi/audio/LoopbackAudio.qml | 32 ++++++++++++++---- .../qml/hifi/audio/PlaySampleSound.qml | 6 ++-- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index aa64af22a3..bcbd253e24 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -98,13 +98,6 @@ Rectangle { Separator { } - RalewayRegular { - x: margins.paddings + muteMic.boxSize + muteMic.spacing; - size: 16; - color: "white"; - text: qsTr("Input Device Settings") - } - ColumnLayout { x: margins.paddings; spacing: 16; @@ -171,7 +164,7 @@ Rectangle { HiFiGlyphs { width: margins.sizeCheckBox text: hifi.glyphs.mic; - color: hifi.colors.primaryHighlight; + color: hifi.colors.white; anchors.left: parent.left anchors.leftMargin: -size/4 //the glyph has empty space at left about 25% anchors.verticalCenter: parent.verticalCenter; @@ -183,8 +176,8 @@ Rectangle { anchors.left: parent.left anchors.leftMargin: margins.sizeCheckBox size: 16; - color: hifi.colors.lightGrayText; - text: qsTr("CHOOSE INPUT DEVICE"); + color: hifi.colors.white; + text: qsTr("Choose input device"); } } @@ -233,6 +226,13 @@ Rectangle { } } } + LoopbackAudio { + x: margins.paddings + + visible: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR); + anchors { left: parent.left; leftMargin: margins.paddings } + } Separator {} @@ -247,7 +247,7 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter; width: margins.sizeCheckBox text: hifi.glyphs.unmuted; - color: hifi.colors.primaryHighlight; + color: hifi.colors.white; size: 36; } @@ -257,8 +257,8 @@ Rectangle { anchors.leftMargin: margins.sizeCheckBox anchors.verticalCenter: parent.verticalCenter; size: 16; - color: hifi.colors.lightGrayText; - text: qsTr("CHOOSE OUTPUT DEVICE"); + color: hifi.colors.white; + text: qsTr("Choose output device"); } } @@ -300,12 +300,5 @@ Rectangle { (bar.currentIndex === 0 && !isVR); anchors { left: parent.left; leftMargin: margins.paddings } } - LoopbackAudio { - x: margins.paddings - - visible: (bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR); - anchors { left: parent.left; leftMargin: margins.paddings } - } } } diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml index 3ecf09c948..ed416656b0 100644 --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -24,7 +24,7 @@ RowLayout { AudioScriptingInterface.setServerEcho(true); } } - function stopAudioLoopback () { + function stopAudioLoopback() { if (audioLoopedBack) { audioLoopedBack = false; AudioScriptingInterface.setServerEcho(false); @@ -33,6 +33,16 @@ RowLayout { HifiConstants { id: hifi; } + Timer { + id: loopbackTimer + interval: 8000; + running: false; + repeat: false; + onTriggered: { + stopAudioLoopback(); + } + } + Button { id: control background: Rectangle { @@ -42,27 +52,35 @@ RowLayout { gradient: Gradient { GradientStop { position: 0.2; - color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black]; + color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : "#FFFFFF"; } GradientStop { position: 1.0; - color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black]; + color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : "#AFAFAF"; } } } contentItem: HiFiGlyphs { size: 14; - color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "white"; - text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.playback_play; + color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "#404040"; + text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.mic; } - onClicked: audioLoopedBack ? stopAudioLoopback() : startAudioLoopback(); + onClicked: { + if (audioLoopedBack) { + loopbackTimer.stop(); + stopAudioLoopback(); + } else { + loopbackTimer.restart(); + startAudioLoopback(); + } + } } RalewayRegular { Layout.leftMargin: 2; size: 14; color: "white"; - text: audioLoopedBack ? qsTr("Disable Audio Loopback") : qsTr("Enable Audio Loopback"); + text: audioLoopedBack ? qsTr("Stop testing your voice") : qsTr("Test your voice"); } } diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index cfe55af9c4..9889d2c6ca 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -64,11 +64,11 @@ RowLayout { gradient: Gradient { GradientStop { position: 0.2; - color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : hifi.buttons.colorStart[hifi.buttons.black]; + color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : "#FFFFFF"; } GradientStop { position: 1.0; - color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : hifi.buttons.colorFinish[hifi.buttons.black]; + color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : "#AFAFAF"; } } } @@ -77,7 +77,7 @@ RowLayout { // x: isPlaying ? 0 : 1; // y: 1; size: 14; - color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "white"; + color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "#404040"; text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play; } From 0f2792930a3d4ba0edda89e8d0330081d16578b5 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 4 Mar 2019 19:28:12 -0800 Subject: [PATCH 031/132] adding switches and buttons to testing audio --- .../resources/qml/controlsUit/Switch.qml | 3 +- interface/resources/qml/hifi/audio/Audio.qml | 81 +++++++++++-------- .../qml/hifi/audio/LoopbackAudio.qml | 31 ++----- .../qml/hifi/audio/PlaySampleSound.qml | 35 ++------ 4 files changed, 62 insertions(+), 88 deletions(-) diff --git a/interface/resources/qml/controlsUit/Switch.qml b/interface/resources/qml/controlsUit/Switch.qml index 0961ef2500..0de95a7e70 100644 --- a/interface/resources/qml/controlsUit/Switch.qml +++ b/interface/resources/qml/controlsUit/Switch.qml @@ -27,6 +27,7 @@ Item { property string labelGlyphOnText: ""; property int labelGlyphOnSize: 32; property alias checked: originalSwitch.checked; + property string backgroundOnColor: "#252525"; signal onCheckedChanged; signal clicked; @@ -54,7 +55,7 @@ Item { } background: Rectangle { - color: "#252525"; + color: checked ? backgroundOnColor : "#252525"; implicitWidth: rootSwitch.switchWidth; implicitHeight: rootSwitch.height; radius: rootSwitch.switchRadius; diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index bcbd253e24..fc0c4d2d43 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -16,7 +16,7 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import controlsUit 1.0 as HifiControlsUit import "../../windows" import "./" as AudioControls @@ -27,6 +27,8 @@ Rectangle { property var eventBridge; property string title: "Audio Settings" + property int switchHeight: 16 + property int switchWidth: 40 signal sendToScript(var message); color: hifi.colors.baseGray; @@ -38,7 +40,7 @@ Rectangle { property bool isVR: AudioScriptingInterface.context === "VR" - property real rightMostInputLevelPos: 0 + property real rightMostInputLevelPos: 450 //placeholder for control sizes and paddings //recalculates dynamically in case of UI size is changed QtObject { @@ -98,58 +100,68 @@ Rectangle { Separator { } - ColumnLayout { - x: margins.paddings; - spacing: 16; + RowLayout { + x: 2 * margins.paddings; + spacing: columnOne.width; width: parent.width; // mute is in its own row - RowLayout { - spacing: (margins.sizeCheckBox - 10.5) * 3; - AudioControls.CheckBox { - id: muteMic - text: qsTr("Mute microphone"); - spacing: margins.sizeCheckBox - boxSize - isRedCheck: true; + ColumnLayout { + id: columnOne + spacing: 12; + x: margins.paddings + HifiControlsUit.Switch { + id: muteMic; + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: "Mute microphone"; + backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.muted; - onClicked: { + onCheckedChanged: { AudioScriptingInterface.muted = checked; checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } } - AudioControls.CheckBox { - id: stereoMic - spacing: muteMic.spacing; - text: qsTr("Enable stereo input"); + HifiControlsUit.Switch { + id: stereoInput; + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Stereo input"); + backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.isStereoInput; - onClicked: { + onCheckedChanged: { AudioScriptingInterface.isStereoInput = checked; checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding } } } - RowLayout { - spacing: muteMic.spacing*2; //make it visually distinguish - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Enable noise reduction"); + ColumnLayout { + spacing: 12; + HifiControlsUit.Switch { + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: "Noise Reduction"; + backgroundOnColor: "#E3E3E3"; checked: AudioScriptingInterface.noiseReduction; - onClicked: { + onCheckedChanged: { AudioScriptingInterface.noiseReduction = checked; checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); + + HifiControlsUit.Switch { + id: audioLevelSwitch + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Audio Level Meter"); + backgroundOnColor: "#E3E3E3"; checked: AvatarInputs.showAudioTools; - onClicked: { + onCheckedChanged: { AvatarInputs.showAudioTools = checked; checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding } - onXChanged: rightMostInputLevelPos = x + width } } } @@ -203,7 +215,7 @@ Rectangle { width: parent.width - inputLevel.width clip: true checkable: !checked - checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD; + checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD; boxSize: margins.sizeCheckBox / 2 isRound: true text: devicename @@ -215,7 +227,7 @@ Rectangle { } } } - InputPeak { + AudioControls.InputPeak { id: inputLevel anchors.right: parent.right peak: model.peak; @@ -225,8 +237,11 @@ Rectangle { AudioScriptingInterface.devices.input.peakValuesAvailable; } } + Component.onCompleted: { + console.log("width " + rightMostInputLevelPos); + } } - LoopbackAudio { + AudioControls.LoopbackAudio { x: margins.paddings visible: (bar.currentIndex === 1 && isVR) || @@ -293,7 +308,7 @@ Rectangle { } } } - PlaySampleSound { + AudioControls.PlaySampleSound { x: margins.paddings visible: (bar.currentIndex === 1 && isVR) || diff --git a/interface/resources/qml/hifi/audio/LoopbackAudio.qml b/interface/resources/qml/hifi/audio/LoopbackAudio.qml index ed416656b0..8ec0ffc496 100644 --- a/interface/resources/qml/hifi/audio/LoopbackAudio.qml +++ b/interface/resources/qml/hifi/audio/LoopbackAudio.qml @@ -14,7 +14,7 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import controlsUit 1.0 as HifiControlsUit RowLayout { property bool audioLoopedBack: AudioScriptingInterface.getServerEcho(); @@ -43,29 +43,9 @@ RowLayout { } } - Button { - id: control - background: Rectangle { - implicitWidth: 20; - implicitHeight: 20; - radius: hifi.buttons.radius; - gradient: Gradient { - GradientStop { - position: 0.2; - color: audioLoopedBack ? hifi.buttons.colorStart[hifi.buttons.blue] : "#FFFFFF"; - } - GradientStop { - position: 1.0; - color: audioLoopedBack ? hifi.buttons.colorFinish[hifi.buttons.blue] : "#AFAFAF"; - } - } - } - contentItem: HiFiGlyphs { - size: 14; - color: (control.pressed || control.hovered) ? (audioLoopedBack ? "black" : hifi.colors.primaryHighlight) : "#404040"; - text: audioLoopedBack ? hifi.glyphs.stop_square : hifi.glyphs.mic; - } - + HifiControlsUit.Button { + text: audioLoopedBack ? qsTr("STOP TESTING YOUR VOICE") : qsTr("TEST YOUR VOICE"); + color: audioLoopedBack ? hifi.buttons.red : hifi.buttons.blue; onClicked: { if (audioLoopedBack) { loopbackTimer.stop(); @@ -81,6 +61,7 @@ RowLayout { Layout.leftMargin: 2; size: 14; color: "white"; - text: audioLoopedBack ? qsTr("Stop testing your voice") : qsTr("Test your voice"); + font.italic: true + text: audioLoopedBack ? qsTr("Speak in your input") : ""; } } diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index 9889d2c6ca..b9d9727dab 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -14,7 +14,7 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import stylesUit 1.0 -import controlsUit 1.0 as HifiControls +import controlsUit 1.0 as HifiControlsUit RowLayout { property var sound: null; @@ -55,32 +55,9 @@ RowLayout { HifiConstants { id: hifi; } - Button { - id: control - background: Rectangle { - implicitWidth: 20; - implicitHeight: 20; - radius: hifi.buttons.radius; - gradient: Gradient { - GradientStop { - position: 0.2; - color: isPlaying ? hifi.buttons.colorStart[hifi.buttons.blue] : "#FFFFFF"; - } - GradientStop { - position: 1.0; - color: isPlaying ? hifi.buttons.colorFinish[hifi.buttons.blue] : "#AFAFAF"; - } - } - } - contentItem: HiFiGlyphs { - // absolutely position due to asymmetry in glyph -// x: isPlaying ? 0 : 1; -// y: 1; - size: 14; - color: (control.pressed || control.hovered) ? (isPlaying ? "black" : hifi.colors.primaryHighlight) : "#404040"; - text: isPlaying ? hifi.glyphs.stop_square : hifi.glyphs.playback_play; - } - + HifiControlsUit.Button { + text: isPlaying ? qsTr("STOP TESTING YOUR SOUND") : qsTr("TEST YOUR SOUND"); + color: isPlaying ? hifi.buttons.red : hifi.buttons.blue; onClicked: isPlaying ? stopSound() : playSound(); } @@ -88,7 +65,7 @@ RowLayout { Layout.leftMargin: 2; size: 14; color: "white"; - text: isPlaying ? qsTr("Stop sample sound") : qsTr("Play sample sound"); + font.italic: true + text: isPlaying ? qsTr("Listen to your output") : ""; } - } From a00d6ec9f6565e77db30a32211ed5eaa285aba50 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 4 Mar 2019 19:42:30 -0800 Subject: [PATCH 032/132] removing debug statement --- interface/resources/qml/controlsUit/Switch.qml | 4 ++-- interface/resources/qml/hifi/audio/Audio.qml | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/controlsUit/Switch.qml b/interface/resources/qml/controlsUit/Switch.qml index 0de95a7e70..4e1c21c456 100644 --- a/interface/resources/qml/controlsUit/Switch.qml +++ b/interface/resources/qml/controlsUit/Switch.qml @@ -41,10 +41,10 @@ Item { onClicked: rootSwitch.clicked(); hoverEnabled: true - topPadding: 3; + topPadding: 1; leftPadding: 3; rightPadding: 3; - bottomPadding: 3; + bottomPadding: 1; onHoveredChanged: { if (hovered) { diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index fc0c4d2d43..1869fb9b3e 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -108,7 +108,7 @@ Rectangle { // mute is in its own row ColumnLayout { id: columnOne - spacing: 12; + spacing: 24; x: margins.paddings HifiControlsUit.Switch { id: muteMic; @@ -138,7 +138,7 @@ Rectangle { } ColumnLayout { - spacing: 12; + spacing: 24; HifiControlsUit.Switch { height: root.switchHeight; switchWidth: root.switchWidth; @@ -237,9 +237,6 @@ Rectangle { AudioScriptingInterface.devices.input.peakValuesAvailable; } } - Component.onCompleted: { - console.log("width " + rightMostInputLevelPos); - } } AudioControls.LoopbackAudio { x: margins.paddings From f1cf11de8546cf367671f6ae107efc1401e6c1e0 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 5 Mar 2019 10:53:55 -0800 Subject: [PATCH 033/132] moving gain slider to audio settings --- interface/resources/qml/hifi/NameCard.qml | 28 +++----- interface/resources/qml/hifi/audio/Audio.qml | 75 ++++++++++++++++++-- 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 242ca5ab57..646fc881e1 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -376,7 +376,7 @@ Item { } FiraSansRegular { id: nameCardConnectionInfoText - visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard + visible: selected && !isMyCard && pal.activeTab == "connectionsTab" width: parent.width height: displayNameTextPixelSize size: displayNameTextPixelSize - 4 @@ -412,7 +412,7 @@ Item { } FiraSansRegular { id: nameCardRemoveConnectionText - visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard + visible: selected && !isMyCard && pal.activeTab == "connectionsTab" width: parent.width height: displayNameTextPixelSize size: displayNameTextPixelSize - 4 @@ -425,7 +425,7 @@ Item { } HifiControls.Button { id: visitConnectionButton - visible: selected && !isMyCard && pal.activeTab == "connectionsTab" && !isMyCard + visible: selected && !isMyCard && pal.activeTab == "connectionsTab" text: "Visit" enabled: thisNameCard.placeName !== "" anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter @@ -450,7 +450,7 @@ Item { // Style radius: 4 color: "#c5c5c5" - visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent + visible: (!isMyCard && (selected && pal.activeTab == "nearbyTab")) && isPresent // Rectangle for the zero-gain point on the VU meter Rectangle { id: vuMeterZeroGain @@ -481,7 +481,7 @@ Item { id: vuMeterBase // Anchors anchors.fill: parent - visible: isMyCard || selected + visible: !isMyCard && selected // Style color: parent.color radius: parent.radius @@ -489,7 +489,7 @@ Item { // Rectangle for the VU meter audio level Rectangle { id: vuMeterLevel - visible: isMyCard || selected + visible: !isMyCard && selected // Size width: (thisNameCard.audioLevel) * parent.width // Style @@ -525,7 +525,7 @@ Item { anchors.verticalCenter: nameCardVUMeter.verticalCenter; anchors.left: nameCardVUMeter.left; // Properties - visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent; + visible: (!isMyCard && (selected && pal.activeTab == "nearbyTab")) && isPresent; minimumValue: -60.0 maximumValue: 20.0 stepSize: 5 @@ -572,19 +572,7 @@ Item { implicitHeight: 16 } } - RalewayRegular { - // The slider for my card is special, it controls the master gain - id: gainSliderText; - visible: isMyCard; - text: "master volume"; - size: hifi.fontSizes.tabularData; - anchors.left: parent.right; - anchors.leftMargin: 8; - color: hifi.colors.baseGrayHighlight; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignTop; - } - } + } function updateGainFromQML(avatarUuid, sliderValue, isReleased) { if (Users.getAvatarGain(avatarUuid) != sliderValue) { diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 1869fb9b3e..efbf663838 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 +import QtQuick 2.7 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 @@ -26,6 +26,8 @@ Rectangle { HifiConstants { id: hifi; } property var eventBridge; + // leave as blank, this is user's volume for the avatar mixer + property var myAvatarUuid: "" property string title: "Audio Settings" property int switchHeight: 16 property int switchWidth: 40 @@ -82,16 +84,16 @@ Rectangle { }); } - function disablePeakValues() { - root.showPeaks = false; - AudioScriptingInterface.devices.input.peakValuesEnabled = false; + function updateMyAvatarGainFromQML(sliderValue, isReleased) { + if (Users.getAvatarGain(myAvatarUuid) != sliderValue) { + Users.setAvatarGain(myAvatarUuid, sliderValue); + } } Component.onCompleted: enablePeakValues(); - Component.onDestruction: disablePeakValues(); - onVisibleChanged: visible ? enablePeakValues() : disablePeakValues(); Column { + id: column spacing: 12; anchors.top: bar.bottom anchors.bottom: parent.bottom @@ -305,6 +307,67 @@ Rectangle { } } } + + Item { + id: gainContainer + x: margins.paddings; + width: parent.width - margins.paddings*2 + height: gainSliderTextMetrics.height + + HifiControlsUit.Slider { + id: gainSlider + anchors.right: parent.right + height: parent.height + width: 200 + minimumValue: -60.0 + maximumValue: 20.0 + stepSize: 5 + value: Users.getAvatarGain(myAvatarUuid) + onValueChanged: { + updateMyAvatarGainFromQML(value, false); + } + onPressedChanged: { + if (!pressed) { + updateMyAvatarGainFromQML(value, false); + } + } + + MouseArea { + anchors.fill: parent + onWheel: { + // Do nothing. + } + onDoubleClicked: { + gainSlider.value = 0.0 + } + onPressed: { + // Pass through to Slider + mouse.accepted = false + } + onReleased: { + // the above mouse.accepted seems to make this + // never get called, nonetheless... + mouse.accepted = false + } + } + } + TextMetrics { + id: gainSliderTextMetrics + text: gainSliderText.text + font: gainSliderText.font + } + RalewayRegular { + // The slider for my card is special, it controls the master gain + id: gainSliderText; + text: "Avatar volume"; + size: 16; + anchors.left: parent.left; + color: hifi.colors.white; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignTop; + } + } + AudioControls.PlaySampleSound { x: margins.paddings From 2020b757a3b536e070a0a8c9cb1dd25344a88c7d Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Tue, 5 Mar 2019 10:58:26 -0800 Subject: [PATCH 034/132] adding mic glyph --- .../hifi-glyphs-1.34/fonts/hifi-glyphs.woff | Bin 21976 -> 0 bytes .../fonts/hifi-glyphs.eot | Bin 34130 -> 34726 bytes .../fonts/hifi-glyphs.svg | 9 ++- .../fonts/hifi-glyphs.ttf | Bin 33952 -> 34548 bytes .../hifi-glyphs-1.38/fonts/hifi-glyphs.woff | Bin 0 -> 22380 bytes .../icons-reference.html | 76 ++++++++++++------ .../styles.css | 27 ++++--- interface/resources/fonts/hifi-glyphs.ttf | Bin 34396 -> 34548 bytes 8 files changed, 74 insertions(+), 38 deletions(-) delete mode 100644 interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.woff rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/fonts/hifi-glyphs.eot (83%) rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/fonts/hifi-glyphs.svg (96%) rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/fonts/hifi-glyphs.ttf (83%) create mode 100644 interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.woff rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/icons-reference.html (97%) rename interface/resources/fonts/{hifi-glyphs-1.34 => hifi-glyphs-1.38}/styles.css (97%) diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.woff b/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.woff deleted file mode 100644 index 6d469701366c67d5aa8123d2be5b314f3f27b65a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21976 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 diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.eot b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.eot similarity index 83% rename from interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.eot rename to interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.eot index 6cdc2487a68d6377f9fdd2fe51ca5c1b9a27b7ad..88936e6c5128623e596c932f5a46a40cd4842a23 100644 GIT binary patch delta 2035 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 diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.svg b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.svg similarity index 96% rename from interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.svg rename to interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.svg index de91dcae71..a3a1aac8a9 100644 --- a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.svg +++ b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.svg @@ -14,7 +14,6 @@ - @@ -97,7 +96,6 @@ - @@ -137,7 +135,6 @@ - @@ -156,4 +153,10 @@ + + + + + + diff --git a/interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.ttf similarity index 83% rename from interface/resources/fonts/hifi-glyphs-1.34/fonts/hifi-glyphs.ttf rename to interface/resources/fonts/hifi-glyphs-1.38/fonts/hifi-glyphs.ttf index c27553333bda29cdfd06e4c1ebdd964b83ff6175..dae388d2eb6798d7d0d0f4135609908d83141764 100644 GIT binary patch delta 2032 zcmY+FYfM|`8OQ&xZH|wx*v3A-U=w3};n*18?}l7T2oMOQaadat5@HC1*uhDhl%#-Z zl9Dx{3FS02O|vPR&{nZDOGmUtQ>C?2R_#NlRcOk(Et8_DtEPR3I+dYynw04n)~QE2 z|M!26p67j^-}B*lZ@ns5eN9jT0ieJ%gy?B)&2&x9%-#oPJ_TUj(Xzc2zXk=&+y`Lo z-s1~)zu!3pkmmsOX7dAu`8!v80l^o5;N0-Q$->j85CSuI0no#_Gl&1Ae)1(i-3DG< z92puI)C{}-3g8id=*Y-WX+Y#Bj{%|uU?0gB$7?&Q&j92uK$>%7*@3U#(|#YQC;}v& z9~dv-E7H$ZL0Lqrbp$e^))#MI*iXAoLS5kwI~97z^q7gf>7oFIQ zT?m(8h7clDfPxq_BzOx_$RLLTN>oAxH8iNgMQEV|13e5dVmErR2N^tzZ{aySj~CE~ zH?a@<(T@Qf!XSn)j1e5cD2^hBJdR-u1sul-oWv=d#)}xo8JtDnB}`xvQ&>O=(|8%@ zZ~^D>3TE(ayoOhC370X8E8y@3uHgo*bNwP$e2S5~IB;-F zIuQ&7y4%`&Ox2dxug+x;*KD%Z+Uo2Mr>}mVb^9BtZr&9(3q=)_n3lXHmB|&# zN|jnuby2Hh^akVZ-aQ%aAEJr*3Kf^HP?ie9N1{Q|QiYPDsgJ~?;yZK)y(EF8Q!*=g zC{;@R(hljY^uF{H>Az%+vQgO&Lb5rxEDpNIA z^|3asUC{nbcUJcSvyXYetm)JGOZpXq$xtxdFsv9g#wp`%<9*`;;}a88GPRiIO`lgY z)os<+&HF4;%Ser;=JcjFtd#Xu?P1%)x@kM?yBw5b%5llD;P|Cu&3VQ7iOc4ib$wRf zR{t?O?%w7uxZm->Gu=?r@NC0W!_U1fK7sFw@1}3bPy56E8UJ4b+Q3NQgP=8dD0nY) zAxwpDNA^WNkEWw_OR?tIt=QwZHQpJ|#eWiCNwg#$BsIywM(#lg6GDK)=8S}6(NIv&=(J)=XrpvmCK!!`T~0Cg zi(x|x5$qg|#M-;U;Yh4KmC2mWxY@lT7pK_-`Tmj!eb`JclMhi14^n6ch|?7bJ3YiErYwZwMXP60Bp7WX zVOKa7O_89Uv5<0vUQ2n1&Dli4{BK6*vhm?es2uDhV`5dDrC5tus<2orY>tipUZXKm zMvX=&&WVM~_L4DYwA*4_j@H*L&E!Nu|-KOi@BE z8|-$2hZng*663RC5(Pg^c#!{)51~LDZ9wcBrP%)mw86EG6f2dXc;K(OLJ_g&G*)`f!1dU<@yBaMXby*|iyz1;L@kRcwu zTBX;k)cT&dyOEa5>GQNqMmM_Sa$4=%=98-Aq|c{f7?sb(Hu7tuFkYXg?J|{Iu9Dfs zX*NmAh&Ep1cGtvcnH1n!y7zOn6Hb>C7uQENM3CX54POBn{=VTU$cU0VHc{*9M1mjd zA_+ubaA(6;fWgX!r@&y1tDMwxznM5t8G#oecoBpj0VKH1lU6$74S9q9K!SU3(#c(& zY~%hgX(exS|DL=qf{pCt7N*)L{D>6DaqhFJR*GCB3*=4CUFujKFRcoekDp5t?#Jhs z!iwz3Q1+-dd*nnmH#A6{7&`0q2dLaw_UO delta 1476 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 j+LVQgf^blu7y8;nlmGw#c${NkW

HiFi Glyphs

-

This font was created for use in High Fidelity

+

This font was created withFontastic

CSS mapping

  • @@ -43,10 +43,6 @@
  • -
  • -
    - -
  • @@ -375,10 +371,6 @@
  • -
  • -
    - -
  • @@ -535,10 +527,6 @@
  • -
  • -
    - -
  • @@ -611,6 +599,30 @@
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +

Character mapping

    @@ -642,10 +654,6 @@
    -
  • -
    - -
  • @@ -974,10 +982,6 @@
  • -
  • -
    - -
  • @@ -1134,10 +1138,6 @@
  • -
  • -
    - -
  • @@ -1210,6 +1210,30 @@
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
- + \ No newline at end of file diff --git a/interface/resources/fonts/hifi-glyphs-1.34/styles.css b/interface/resources/fonts/hifi-glyphs-1.38/styles.css similarity index 97% rename from interface/resources/fonts/hifi-glyphs-1.34/styles.css rename to interface/resources/fonts/hifi-glyphs-1.38/styles.css index 59e38d455c..f6a096189b 100644 --- a/interface/resources/fonts/hifi-glyphs-1.34/styles.css +++ b/interface/resources/fonts/hifi-glyphs-1.38/styles.css @@ -59,9 +59,6 @@ .icon-headphones:before { content: "\68"; } -.icon-mic:before { - content: "\69"; -} .icon-upload:before { content: "\6a"; } @@ -308,9 +305,6 @@ .icon-voxels:before { content: "\e005"; } -.icon-lock:before { - content: "\e006"; -} .icon-visible:before { content: "\e007"; } @@ -428,9 +422,6 @@ .icon-password:before { content: "\e029"; } -.icon-rez:before { - content: "\e025"; -} .icon-keyboard-collapse:before { content: "\e02b"; } @@ -485,3 +476,21 @@ .icon-oculus:before { content: "\e036"; } +.icon-check-circled:before { + content: "\e037"; +} +.icon-rez-01:before { + content: "\e025"; +} +.icon-locked:before { + content: "\e006"; +} +.icon-unlocked:before { + content: "\e039"; +} +.icon-mic:before { + content: "\69"; +} +.icon-92-lock-01:before { + content: "\e038"; +} diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index aaeb1d2ace9c0f76f01de9035c05cf1af5f44df3..dae388d2eb6798d7d0d0f4135609908d83141764 100644 GIT binary patch delta 1790 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 From 1941e53cc3c62b57704af320ff12a13f56755c68 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 4 Mar 2019 15:45:47 -0800 Subject: [PATCH 035/132] track oculus controllers while in oculus home --- .../qml/hifi/tablet/OculusConfiguration.qml | 59 +++++++++++++++++++ interface/src/ui/PreferencesDialog.cpp | 1 + .../oculus/src/OculusControllerManager.cpp | 20 ++++++- plugins/oculus/src/OculusControllerManager.h | 6 ++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 interface/resources/qml/hifi/tablet/OculusConfiguration.qml diff --git a/interface/resources/qml/hifi/tablet/OculusConfiguration.qml b/interface/resources/qml/hifi/tablet/OculusConfiguration.qml new file mode 100644 index 0000000000..6df4fa1b83 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/OculusConfiguration.qml @@ -0,0 +1,59 @@ +// +// Created by Dante Ruiz on 3/4/19. +// Copyright 2019 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.5 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls + + +Rectangle { + id: root + anchors.fill: parent + property string pluginName: "" + property var displayInformation: null + HifiConstants { id: hifi } + + color: hifi.colors.baseGray + + HifiControls.CheckBox { + id: box + width: 15 + height: 15 + + anchors { + left: root.left + leftMargin: 75 + } + + onClicked: { + sendConfigurationSettings( { trackControllersInOculusHome: checked }); + } + } + + RalewaySemiBold { + id: head + + text: "Track hand controllers in Oculus Home" + size: 12 + + color: "white" + anchors.left: box.right + anchors.leftMargin: 5 + } + + function displayConfiguration() { + var configurationSettings = InputConfiguration.configurationSettings(root.pluginName); + box.checked = configurationSettings.trackControllersInOculusHome; + } + + function sendConfigurationSettings(settings) { + InputConfiguration.setConfigurationSettings(settings, root.pluginName); + } +} diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 3d7971cf57..dbd24573ee 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "Application.h" diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index c2b9145f3c..8d97ff78af 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -30,6 +30,7 @@ using namespace hifi; const char* OculusControllerManager::NAME = "Oculus"; +const QString OCULUS_LAYOUT = "OculusConfiguration.qml"; const quint64 LOST_TRACKING_DELAY = 3000000; @@ -43,6 +44,22 @@ bool OculusControllerManager::activate() { return true; } +QString OculusControllerManager::configurationLayout() { + return OCULUS_LAYOUT; +} + +void OculusControllerManager::setConfigurationSettings(const QJsonObject configurationSettings) { + if (configurationSettings.contains("trackControllersInOculusHome")) { + _touch->_trackControllersInOculusHome.set(configurationSettings["trackControllersInOculusHome"].toBool()); + } +} + +QJsonObject OculusControllerManager::configurationSettings() { + QJsonObject configurationSettings; + configurationSettings["trackControllersInOculusHome"] = _touch->_trackControllersInOculusHome.get(); + return configurationSettings; +} + void OculusControllerManager::checkForConnectedDevices() { if (_touch && _remote) { return; @@ -215,13 +232,14 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, quint64 currentTime = usecTimestampNow(); static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked | ovrStatus_PositionTracked; bool hasInputFocus = ovr::hasInputFocus(); + bool trackControllersInOculusHome = _trackControllersInOculusHome.get(); auto tracking = ovr::getTrackingState(); // ovr_GetTrackingState(_parent._session, 0, false); ovr::for_each_hand([&](ovrHandType hand) { ++numTrackedControllers; int controller = (hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND); // Disable hand tracking while in Oculus Dash (Dash renders it's own hands) - if (!hasInputFocus) { + if (!hasInputFocus && !trackControllersInOculusHome) { _poseStateMap.erase(controller); _poseStateMap[controller].valid = false; return; diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index ee06115b26..ea32eace61 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -15,6 +15,7 @@ #include +#include #include #include @@ -28,7 +29,11 @@ public: const QString getName() const override { return NAME; } bool isHandController() const override { return _touch != nullptr; } bool isHeadController() const override { return true; } + bool configurable() override { return true; } + QString configurationLayout() override; QStringList getSubdeviceNames() override; + void setConfigurationSettings(const QJsonObject configurationSetting) override; + QJsonObject configurationSettings() override; bool activate() override; void deactivate() override; @@ -93,6 +98,7 @@ private: float _leftHapticStrength { 0.0f }; float _rightHapticDuration { 0.0f }; float _rightHapticStrength { 0.0f }; + Setting::Handle _trackControllersInOculusHome { "trackControllersInOculusHome", false }; mutable std::recursive_mutex _lock; std::map _lostTracking; std::map _regainTrackingDeadline; From 23e8a8bed4c6f086fa5718153cf6733ae218ae68 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 036/132] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++ interface/resources/qml/hifi/audio/MicBar.qml | 9 +- interface/src/Application.cpp | 18 ++ interface/src/Application.h | 2 + interface/src/scripting/Audio.cpp | 174 +++++++++++++++++- interface/src/scripting/Audio.h | 89 ++++++++- scripts/system/audio.js | 3 + 7 files changed, 302 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..45358f59a2 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -148,6 +148,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 39f75a9182..f91058bc3c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -11,10 +11,13 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + HifiConstants { id: hifi; } + readonly property var level: AudioScriptingInterface.inputLevel; property bool gated: false; @@ -131,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.muted; + visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -152,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.muted ? "MUTED" : "MUTE"; + text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2c8d71af00..bcd5367a89 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1431,6 +1431,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::pushedToTalk, + reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -4195,6 +4197,10 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_T: + emit pushedToTalk(true); + break; + case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4300,6 +4306,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } + + switch (event->key()) { + case Qt::Key_T: + emit pushedToTalk(false); + break; + } } void Application::focusOutEvent(QFocusEvent* event) { @@ -5243,6 +5255,9 @@ void Application::loadSettings() { } } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->loadData(); + getMyAvatar()->loadData(); _settingsLoaded = true; } @@ -5252,6 +5267,9 @@ void Application::saveSettings() const { DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->saveData(); + Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a8cc9450c5..1c86326f90 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,6 +358,8 @@ signals: void miniTabletEnabledChanged(bool enabled); + void pushedToTalk(bool enabled); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..fe04ce47ca 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -63,26 +63,163 @@ void Audio::stopRecording() { } bool Audio::isMuted() const { - return resultWithReadLock([&] { - return _isMuted; - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getMutedHMD(); + } + else { + return getMutedDesktop(); + } } void Audio::setMuted(bool isMuted) { + withWriteLock([&] { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } + else { + setMutedDesktop(isMuted); + } + }); +} + +void Audio::setMutedDesktop(bool isMuted) { + bool changed = false; + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit desktopMutedChanged(isMuted); + } +} + +bool Audio::getMutedDesktop() const { + return resultWithReadLock([&] { + return _desktopMuted; + }); +} + +void Audio::setMutedHMD(bool isMuted) { + bool changed = false; + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit hmdMutedChanged(isMuted); + } +} + +bool Audio::getMutedHMD() const { + return resultWithReadLock([&] { + return _hmdMuted; + }); +} + +bool Audio::getPTT() { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getPTTHMD(); + } + else { + return getPTTDesktop(); + } +} + +bool Audio::getPushingToTalk() const { + return resultWithReadLock([&] { + return _pushingToTalk; + }); +} + +void Audio::setPTT(bool enabled) { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setPTTHMD(enabled); + } + else { + setPTTDesktop(enabled); + } +} + +void Audio::setPTTDesktop(bool enabled) { bool changed = false; withWriteLock([&] { - if (_isMuted != isMuted) { - _isMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (_pttDesktop != enabled) { changed = true; + _pttDesktop = enabled; + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } } }); if (changed) { - emit mutedChanged(isMuted); + emit pushToTalkChanged(enabled); + emit pushToTalkDesktopChanged(enabled); } } +bool Audio::getPTTDesktop() const { + return resultWithReadLock([&] { + return _pttDesktop; + }); +} + +void Audio::setPTTHMD(bool enabled) { + bool changed = false; + withWriteLock([&] { + if (_pttHMD != enabled) { + changed = true; + _pttHMD = enabled; + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + } + }); + if (changed) { + emit pushToTalkChanged(enabled); + emit pushToTalkHMDChanged(enabled); + } +} + +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -179,11 +316,32 @@ void Audio::onContextChanged() { changed = true; } }); + if (isHMD) { + setMuted(getMutedHMD()); + } + else { + setMuted(getMutedDesktop()); + } if (changed) { emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); } } +void Audio::handlePushedToTalk(bool enabled) { + if (getPTT()) { + if (enabled) { + setMuted(false); + } + else { + setMuted(true); + } + if (_pushingToTalk != enabled) { + _pushingToTalk = enabled; + emit pushingToTalkChanged(enabled); + } + } +} + void Audio::setReverb(bool enable) { withWriteLock([&] { DependencyManager::get()->setReverb(enable); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..6aa589e399 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -12,6 +12,7 @@ #ifndef hifi_scripting_Audio_h #define hifi_scripting_Audio_h +#include #include "AudioScriptingInterface.h" #include "AudioDevices.h" #include "AudioEffectOptions.h" @@ -19,6 +20,9 @@ #include "AudioFileWav.h" #include +using MutedGetter = std::function; +using MutedSetter = std::function; + namespace scripting { class Audio : public AudioScriptingInterface, protected ReadWriteLockable { @@ -63,6 +67,12 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) + Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) + Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); + Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) + Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; @@ -82,6 +92,25 @@ public: void showMicMeter(bool show); + // Mute setting setters and getters + void setMutedDesktop(bool isMuted); + bool getMutedDesktop() const; + void setMutedHMD(bool isMuted); + bool getMutedHMD() const; + void setPTT(bool enabled); + bool getPTT(); + bool getPushingToTalk() const; + + // Push-To-Talk setters and getters + void setPTTDesktop(bool enabled); + bool getPTTDesktop() const; + void setPTTHMD(bool enabled); + bool getPTTHMD() const; + + // Settings handlers + void saveData(); + void loadData(); + /**jsdoc * @function Audio.setInputDevice * @param {object} device @@ -193,6 +222,46 @@ signals: */ void mutedChanged(bool isMuted); + /**jsdoc + * Triggered when desktop audio input is muted or unmuted. + * @function Audio.desktopMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. + * @returns {Signal} + */ + void desktopMutedChanged(bool isMuted); + + /**jsdoc + * Triggered when HMD audio input is muted or unmuted. + * @function Audio.hmdMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. + * @returns {Signal} + */ + void hmdMutedChanged(bool isMuted); + + /** + * Triggered when Push-to-Talk has been enabled or disabled. + * @function Audio.pushToTalkChanged + * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. + * @returns {Signal} + */ + void pushToTalkChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. + * @function Audio.pushToTalkDesktopChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkDesktopChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. + * @function Audio.pushToTalkHMDChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkHMDChanged(bool enabled); + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -237,6 +306,14 @@ signals: */ void contextChanged(const QString& context); + /**jsdoc + * Triggered when pushing to talk. + * @function Audio.pushingToTalkChanged + * @param {boolean} talking - true if broadcasting with PTT, false otherwise. + * @returns {Signal} + */ + void pushingToTalkChanged(bool talking); + public slots: /**jsdoc @@ -245,6 +322,8 @@ public slots: */ void onContextChanged(); + void handlePushedToTalk(bool enabled); + private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); @@ -260,11 +339,19 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; bool _isClipping { false }; - bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; + Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; + Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; + Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; + bool _desktopMuted{ true }; + bool _hmdMuted{ false }; + bool _pttDesktop{ false }; + bool _pttHMD{ false }; + bool _pushingToTalk{ false }; }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index ee82c0c6ea..51d070d8cd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -28,6 +28,9 @@ var UNMUTE_ICONS = { }; function onMuteToggled() { + if (Audio.pushingToTalk) { + return; + } if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { From d88235a08f577bc4145bd6b8e518e02e256ae3ce Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 037/132] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++ interface/resources/qml/hifi/audio/MicBar.qml | 9 +- interface/src/Application.cpp | 18 ++ interface/src/Application.h | 2 + interface/src/scripting/Audio.cpp | 174 +++++++++++++++++- interface/src/scripting/Audio.h | 89 ++++++++- scripts/system/audio.js | 3 + 7 files changed, 302 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..45358f59a2 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -148,6 +148,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 39f75a9182..f91058bc3c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -11,10 +11,13 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + HifiConstants { id: hifi; } + readonly property var level: AudioScriptingInterface.inputLevel; property bool gated: false; @@ -131,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.muted; + visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -152,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.muted ? "MUTED" : "MUTE"; + text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8ab4d10db..fa63757560 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1435,6 +1435,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::pushedToTalk, + reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -4199,6 +4201,10 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_T: + emit pushedToTalk(true); + break; + case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4304,6 +4310,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } + + switch (event->key()) { + case Qt::Key_T: + emit pushedToTalk(false); + break; + } } void Application::focusOutEvent(QFocusEvent* event) { @@ -5235,6 +5247,9 @@ void Application::loadSettings() { } } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->loadData(); + getMyAvatar()->loadData(); _settingsLoaded = true; } @@ -5244,6 +5259,9 @@ void Application::saveSettings() const { DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->saveData(); + Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a8cc9450c5..1c86326f90 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,6 +358,8 @@ signals: void miniTabletEnabledChanged(bool enabled); + void pushedToTalk(bool enabled); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..fe04ce47ca 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -63,26 +63,163 @@ void Audio::stopRecording() { } bool Audio::isMuted() const { - return resultWithReadLock([&] { - return _isMuted; - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getMutedHMD(); + } + else { + return getMutedDesktop(); + } } void Audio::setMuted(bool isMuted) { + withWriteLock([&] { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } + else { + setMutedDesktop(isMuted); + } + }); +} + +void Audio::setMutedDesktop(bool isMuted) { + bool changed = false; + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit desktopMutedChanged(isMuted); + } +} + +bool Audio::getMutedDesktop() const { + return resultWithReadLock([&] { + return _desktopMuted; + }); +} + +void Audio::setMutedHMD(bool isMuted) { + bool changed = false; + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit hmdMutedChanged(isMuted); + } +} + +bool Audio::getMutedHMD() const { + return resultWithReadLock([&] { + return _hmdMuted; + }); +} + +bool Audio::getPTT() { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getPTTHMD(); + } + else { + return getPTTDesktop(); + } +} + +bool Audio::getPushingToTalk() const { + return resultWithReadLock([&] { + return _pushingToTalk; + }); +} + +void Audio::setPTT(bool enabled) { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setPTTHMD(enabled); + } + else { + setPTTDesktop(enabled); + } +} + +void Audio::setPTTDesktop(bool enabled) { bool changed = false; withWriteLock([&] { - if (_isMuted != isMuted) { - _isMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (_pttDesktop != enabled) { changed = true; + _pttDesktop = enabled; + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } } }); if (changed) { - emit mutedChanged(isMuted); + emit pushToTalkChanged(enabled); + emit pushToTalkDesktopChanged(enabled); } } +bool Audio::getPTTDesktop() const { + return resultWithReadLock([&] { + return _pttDesktop; + }); +} + +void Audio::setPTTHMD(bool enabled) { + bool changed = false; + withWriteLock([&] { + if (_pttHMD != enabled) { + changed = true; + _pttHMD = enabled; + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + } + }); + if (changed) { + emit pushToTalkChanged(enabled); + emit pushToTalkHMDChanged(enabled); + } +} + +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -179,11 +316,32 @@ void Audio::onContextChanged() { changed = true; } }); + if (isHMD) { + setMuted(getMutedHMD()); + } + else { + setMuted(getMutedDesktop()); + } if (changed) { emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); } } +void Audio::handlePushedToTalk(bool enabled) { + if (getPTT()) { + if (enabled) { + setMuted(false); + } + else { + setMuted(true); + } + if (_pushingToTalk != enabled) { + _pushingToTalk = enabled; + emit pushingToTalkChanged(enabled); + } + } +} + void Audio::setReverb(bool enable) { withWriteLock([&] { DependencyManager::get()->setReverb(enable); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..6aa589e399 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -12,6 +12,7 @@ #ifndef hifi_scripting_Audio_h #define hifi_scripting_Audio_h +#include #include "AudioScriptingInterface.h" #include "AudioDevices.h" #include "AudioEffectOptions.h" @@ -19,6 +20,9 @@ #include "AudioFileWav.h" #include +using MutedGetter = std::function; +using MutedSetter = std::function; + namespace scripting { class Audio : public AudioScriptingInterface, protected ReadWriteLockable { @@ -63,6 +67,12 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) + Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) + Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); + Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) + Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; @@ -82,6 +92,25 @@ public: void showMicMeter(bool show); + // Mute setting setters and getters + void setMutedDesktop(bool isMuted); + bool getMutedDesktop() const; + void setMutedHMD(bool isMuted); + bool getMutedHMD() const; + void setPTT(bool enabled); + bool getPTT(); + bool getPushingToTalk() const; + + // Push-To-Talk setters and getters + void setPTTDesktop(bool enabled); + bool getPTTDesktop() const; + void setPTTHMD(bool enabled); + bool getPTTHMD() const; + + // Settings handlers + void saveData(); + void loadData(); + /**jsdoc * @function Audio.setInputDevice * @param {object} device @@ -193,6 +222,46 @@ signals: */ void mutedChanged(bool isMuted); + /**jsdoc + * Triggered when desktop audio input is muted or unmuted. + * @function Audio.desktopMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. + * @returns {Signal} + */ + void desktopMutedChanged(bool isMuted); + + /**jsdoc + * Triggered when HMD audio input is muted or unmuted. + * @function Audio.hmdMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. + * @returns {Signal} + */ + void hmdMutedChanged(bool isMuted); + + /** + * Triggered when Push-to-Talk has been enabled or disabled. + * @function Audio.pushToTalkChanged + * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. + * @returns {Signal} + */ + void pushToTalkChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. + * @function Audio.pushToTalkDesktopChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkDesktopChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. + * @function Audio.pushToTalkHMDChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkHMDChanged(bool enabled); + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -237,6 +306,14 @@ signals: */ void contextChanged(const QString& context); + /**jsdoc + * Triggered when pushing to talk. + * @function Audio.pushingToTalkChanged + * @param {boolean} talking - true if broadcasting with PTT, false otherwise. + * @returns {Signal} + */ + void pushingToTalkChanged(bool talking); + public slots: /**jsdoc @@ -245,6 +322,8 @@ public slots: */ void onContextChanged(); + void handlePushedToTalk(bool enabled); + private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); @@ -260,11 +339,19 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; bool _isClipping { false }; - bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; + Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; + Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; + Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; + bool _desktopMuted{ true }; + bool _hmdMuted{ false }; + bool _pttDesktop{ false }; + bool _pttHMD{ false }; + bool _pushingToTalk{ false }; }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index ee82c0c6ea..51d070d8cd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -28,6 +28,9 @@ var UNMUTE_ICONS = { }; function onMuteToggled() { + if (Audio.pushingToTalk) { + return; + } if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { From 8b4123b5dc7a92bedd7a533a2fd6f16b66a1ade5 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:00:09 -0800 Subject: [PATCH 038/132] laying groundwork for audio app + fixing deadlocks --- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- interface/src/scripting/Audio.cpp | 108 +++++++++--------- interface/src/scripting/Audio.h | 1 + scripts/system/audio.js | 11 +- 4 files changed, 69 insertions(+), 57 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f91058bc3c..2ab1085408 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,7 +134,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index fe04ce47ca..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -66,32 +66,30 @@ bool Audio::isMuted() const { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getMutedHMD(); - } - else { + } else { return getMutedDesktop(); } } void Audio::setMuted(bool isMuted) { - withWriteLock([&] { - bool isHMD = qApp->isHMDMode(); - if (isHMD) { - setMutedHMD(isMuted); - } - else { - setMutedDesktop(isMuted); - } - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } else { + setMutedDesktop(isMuted); + } } void Audio::setMutedDesktop(bool isMuted) { bool changed = false; - if (_desktopMuted != isMuted) { - changed = true; - _desktopMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit desktopMutedChanged(isMuted); @@ -106,12 +104,14 @@ bool Audio::getMutedDesktop() const { void Audio::setMutedHMD(bool isMuted) { bool changed = false; - if (_hmdMuted != isMuted) { - changed = true; - _hmdMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit hmdMutedChanged(isMuted); @@ -128,12 +128,24 @@ bool Audio::getPTT() { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getPTTHMD(); - } - else { + } else { return getPTTDesktop(); } } +void scripting::Audio::setPushingToTalk(bool pushingToTalk) { + bool changed = false; + withWriteLock([&] { + if (_pushingToTalk != pushingToTalk) { + changed = true; + _pushingToTalk = pushingToTalk; + } + }); + if (changed) { + emit pushingToTalkChanged(pushingToTalk); + } +} + bool Audio::getPushingToTalk() const { return resultWithReadLock([&] { return _pushingToTalk; @@ -144,8 +156,7 @@ void Audio::setPTT(bool enabled) { bool isHMD = qApp->isHMDMode(); if (isHMD) { setPTTHMD(enabled); - } - else { + } else { setPTTDesktop(enabled); } } @@ -156,16 +167,16 @@ void Audio::setPTTDesktop(bool enabled) { if (_pttDesktop != enabled) { changed = true; _pttDesktop = enabled; - if (!enabled) { - // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. - setMutedDesktop(true); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedDesktop(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkDesktopChanged(enabled); @@ -184,16 +195,16 @@ void Audio::setPTTHMD(bool enabled) { if (_pttHMD != enabled) { changed = true; _pttHMD = enabled; - if (!enabled) { - // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. - setMutedHMD(false); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedHMD(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkHMDChanged(enabled); @@ -318,8 +329,7 @@ void Audio::onContextChanged() { }); if (isHMD) { setMuted(getMutedHMD()); - } - else { + } else { setMuted(getMutedDesktop()); } if (changed) { @@ -331,14 +341,10 @@ void Audio::handlePushedToTalk(bool enabled) { if (getPTT()) { if (enabled) { setMuted(false); - } - else { + } else { setMuted(true); } - if (_pushingToTalk != enabled) { - _pushingToTalk = enabled; - emit pushingToTalkChanged(enabled); - } + setPushingToTalk(enabled); } } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 6aa589e399..94f8a7bf54 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -99,6 +99,7 @@ public: bool getMutedHMD() const; void setPTT(bool enabled); bool getPTT(); + void setPushingToTalk(bool pushingToTalk); bool getPushingToTalk() const; // Push-To-Talk setters and getters diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 51d070d8cd..bf44cfa7cc 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -26,12 +26,15 @@ var UNMUTE_ICONS = { icon: "icons/tablet-icons/mic-unmute-i.svg", activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; function onMuteToggled() { if (Audio.pushingToTalk) { - return; - } - if (Audio.muted) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { button.editProperties(UNMUTE_ICONS); @@ -71,6 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); +Audio.pushingToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -79,6 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushingToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 7f585587ffb3afb0073855150fc5026ab0976aa1 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:11:47 -0800 Subject: [PATCH 039/132] Push to talk changes + rebased with master (#7) Push to talk changes + rebased with master --- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- interface/src/AboutUtil.h | 14 +- interface/src/Application.cpp | 24 +- interface/src/commerce/Wallet.h | 8 +- .../AccountServicesScriptingInterface.cpp | 4 +- .../AccountServicesScriptingInterface.h | 34 +-- interface/src/scripting/Audio.cpp | 128 ++++++---- interface/src/scripting/Audio.h | 1 + .../src/scripting/WalletScriptingInterface.h | 22 +- interface/src/ui/overlays/Overlays.cpp | 239 ++++++------------ interface/src/ui/overlays/Overlays.h | 17 +- .../src/EntityTreeRenderer.cpp | 70 ++--- .../src/EntityTreeRenderer.h | 8 +- libraries/entities/src/EntityTree.cpp | 24 +- libraries/entities/src/EntityTree.h | 7 +- libraries/entities/src/EntityTreeElement.cpp | 4 +- libraries/entities/src/EntityTreeElement.h | 2 +- libraries/entities/src/ModelEntityItem.h | 2 +- libraries/fbx/src/FBXSerializer.cpp | 28 +- libraries/octree/src/Octree.h | 2 +- libraries/octree/src/OctreeProcessor.cpp | 4 +- libraries/octree/src/OctreeProcessor.h | 2 +- libraries/render-utils/src/Model.cpp | 19 +- libraries/script-engine/src/ScriptEngine.cpp | 4 +- scripts/system/audio.js | 11 +- 25 files changed, 331 insertions(+), 353 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f91058bc3c..2ab1085408 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,7 +134,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } diff --git a/interface/src/AboutUtil.h b/interface/src/AboutUtil.h index c06255aaa5..4a5074857d 100644 --- a/interface/src/AboutUtil.h +++ b/interface/src/AboutUtil.h @@ -16,8 +16,8 @@ #include /**jsdoc - * The HifiAbout API provides information about the version of Interface that is currently running. It also - * provides the ability to open a Web page in an Interface browser window. + * The HifiAbout API provides information about the version of Interface that is currently running. It also + * has the functionality to open a web page in an Interface browser window. * * @namespace HifiAbout * @@ -30,9 +30,9 @@ * @property {string} qtVersion - The Qt version used in Interface that is currently running. Read-only. * * @example Report build information for the version of Interface currently running. - * print("HiFi build date: " + HifiAbout.buildDate); // 11 Feb 2019 - * print("HiFi version: " + HifiAbout.buildVersion); // 0.78.0 - * print("Qt version: " + HifiAbout.qtVersion); // 5.10.1 + * print("HiFi build date: " + HifiAbout.buildDate); // Returns the build date of the version of Interface currently running on your machine. + * print("HiFi version: " + HifiAbout.buildVersion); // Returns the build version of Interface currently running on your machine. + * print("Qt version: " + HifiAbout.qtVersion); // Returns the Qt version details of the version of Interface currently running on your machine. */ class AboutUtil : public QObject { @@ -52,9 +52,9 @@ public: public slots: /**jsdoc - * Display a Web page in an Interface browser window. + * Display a web page in an Interface browser window. * @function HifiAbout.openUrl - * @param {string} url - The URL of the Web page to display. + * @param {string} url - The URL of the web page you want to view in Interface. */ void openUrl(const QString &url) const; private: diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bcd5367a89..fa63757560 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1061,6 +1061,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(PluginManager::getInstance().data(), &PluginManager::inputDeviceRunningChanged, controllerScriptingInterface, &controller::ScriptingInterface::updateRunningInputDevices); + EntityTree::setEntityClicksCapturedOperator([this] { + return _controllerScriptingInterface->areEntityClicksCaptured(); + }); + _entityClipboard->createRootElement(); #ifdef Q_OS_WIN @@ -4404,7 +4408,6 @@ void Application::mouseMoveEvent(QMouseEvent* event) { if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() || getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_ENTITY_ID) { getEntities()->mouseMoveEvent(&mappedEvent); - getOverlays().mouseMoveEvent(&mappedEvent); } _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent); // send events to any registered scripts @@ -4438,14 +4441,8 @@ void Application::mousePressEvent(QMouseEvent* event) { #endif QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers()); - std::pair entityResult; - if (!_controllerScriptingInterface->areEntityClicksCaptured()) { - entityResult = getEntities()->mousePressEvent(&mappedEvent); - } - std::pair overlayResult = getOverlays().mousePressEvent(&mappedEvent); - - QUuid focusedEntity = entityResult.first < overlayResult.first ? entityResult.second : overlayResult.second; - setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(focusedEntity) ? focusedEntity : UNKNOWN_ENTITY_ID); + QUuid result = getEntities()->mousePressEvent(&mappedEvent); + setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID); _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts @@ -4484,11 +4481,7 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) { transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers()); - - if (!_controllerScriptingInterface->areEntityClicksCaptured()) { - getEntities()->mouseDoublePressEvent(&mappedEvent); - } - getOverlays().mouseDoublePressEvent(&mappedEvent); + getEntities()->mouseDoublePressEvent(&mappedEvent); // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { @@ -4513,7 +4506,6 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { event->buttons(), event->modifiers()); getEntities()->mouseReleaseEvent(&mappedEvent); - getOverlays().mouseReleaseEvent(&mappedEvent); _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts @@ -6969,7 +6961,7 @@ void Application::clearDomainOctreeDetails(bool clearAll) { }); // reset the model renderer - clearAll ? getEntities()->clear() : getEntities()->clearNonLocalEntities(); + clearAll ? getEntities()->clear() : getEntities()->clearDomainAndNonOwnedEntities(); auto skyStage = DependencyManager::get()->getSkyStage(); diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 5e5e6c9b4f..fdd6b5e2a6 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -62,8 +62,8 @@ public: * ValueMeaningDescription * * - * 0Not logged inThe user isn't logged in. - * 1Not set upThe user's wallet isn't set up. + * 0Not logged inThe user is not logged in. + * 1Not set upThe user's wallet has not been set up. * 2Pre-existingThere is a wallet present on the server but not one * locally. * 3ConflictingThere is a wallet present on the server plus one present locally, @@ -73,8 +73,8 @@ public: * 5ReadyThe wallet is ready for use. * * - *

Wallets used to be stored locally but now they're stored on the server, unless the computer once had a wallet stored - * locally in which case the wallet may be present in both places.

+ *

Wallets used to be stored locally but now they're only stored on the server. A wallet is present in both places if + * your computer previously stored its information locally.

* @typedef {number} WalletScriptingInterface.WalletStatus */ enum WalletStatus { diff --git a/interface/src/scripting/AccountServicesScriptingInterface.cpp b/interface/src/scripting/AccountServicesScriptingInterface.cpp index a3597886e9..5f8fb065ff 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.cpp +++ b/interface/src/scripting/AccountServicesScriptingInterface.cpp @@ -115,8 +115,8 @@ DownloadInfoResult::DownloadInfoResult() : /**jsdoc * Information on the assets currently being downloaded and pending download. * @typedef {object} AccountServices.DownloadInfoResult - * @property {number[]} downloading - The percentage complete for each asset currently being downloaded. - * @property {number} pending - The number of assets waiting to be download. + * @property {number[]} downloading - The download percentage remaining of each asset currently downloading. + * @property {number} pending - The number of assets pending download. */ QScriptValue DownloadInfoResultToScriptValue(QScriptEngine* engine, const DownloadInfoResult& result) { QScriptValue object = engine->newObject(); diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index c08181d7c9..b188b4e63b 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -38,19 +38,19 @@ class AccountServicesScriptingInterface : public QObject { Q_OBJECT /**jsdoc - * The AccountServices API provides functions related to user connectivity, visibility, and asset download - * progress. + * The AccountServices API provides functions that give information on user connectivity, visibility, and + * asset download progress. * * @hifi-interface * @hifi-client-entity * @hifi-avatar * * @namespace AccountServices - * @property {string} username - The user name if the user is logged in, otherwise "Unknown user". - * Read-only. + * @property {string} username - The user name of the user logged in. If there is no user logged in, it is + * "Unknown user". Read-only. * @property {boolean} loggedIn - true if the user is logged in, otherwise false. * Read-only. - * @property {string} findableBy - The user's visibility to other people:
+ * @property {string} findableBy - The user's visibility to other users:
* "none" - user appears offline.
* "friends" - user is visible only to friends.
* "connections" - user is visible to friends and connections.
@@ -74,23 +74,23 @@ public: public slots: /**jsdoc - * Get information on the progress of downloading assets in the domain. + * Gets information on the download progress of assets in the domain. * @function AccountServices.getDownloadInfo - * @returns {AccountServices.DownloadInfoResult} Information on the progress of assets download. + * @returns {AccountServices.DownloadInfoResult} Information on the download progress of assets. */ DownloadInfoResult getDownloadInfo(); /**jsdoc - * Cause a {@link AccountServices.downloadInfoChanged|downloadInfoChanged} signal to be triggered with information on the - * current progress of the download of assets in the domain. + * Triggers a {@link AccountServices.downloadInfoChanged|downloadInfoChanged} signal with information on the current + * download progress of the assets in the domain. * @function AccountServices.updateDownloadInfo */ void updateDownloadInfo(); /**jsdoc - * Check whether the user is logged in. + * Checks whether the user is logged in. * @function AccountServices.isLoggedIn - * @returns {boolean} true if the user is logged in, false otherwise. + * @returns {boolean} true if the user is logged in, false if not. * @example Report whether you are logged in. * var isLoggedIn = AccountServices.isLoggedIn(); * print("You are logged in: " + isLoggedIn); // true or false @@ -98,9 +98,9 @@ public slots: bool isLoggedIn(); /**jsdoc - * Prompts the user to log in (the login dialog is displayed) if they're not already logged in. + * The function returns the login status of the user and prompts the user to log in (with a login dialog) if they're not already logged in. * @function AccountServices.checkAndSignalForAccessToken - * @returns {boolean} true if the user is already logged in, false otherwise. + * @returns {boolean} true if the user is logged in, false if not. */ bool checkAndSignalForAccessToken(); @@ -140,7 +140,7 @@ signals: /**jsdoc * Triggered when the username logged in with changes, i.e., when the user logs in or out. * @function AccountServices.myUsernameChanged - * @param {string} username - The username logged in with if the user is logged in, otherwise "". + * @param {string} username - The user name of the user logged in. If there is no user logged in, it is "". * @returns {Signal} * @example Report when your username changes. * AccountServices.myUsernameChanged.connect(function (username) { @@ -150,9 +150,9 @@ signals: void myUsernameChanged(const QString& username); /**jsdoc - * Triggered when the progress of the download of assets for the domain changes. + * Triggered when the download progress of the assets in the domain changes. * @function AccountServices.downloadInfoChanged - * @param {AccountServices.DownloadInfoResult} downloadInfo - Information on the progress of assets download. + * @param {AccountServices.DownloadInfoResult} downloadInfo - Information on the download progress of assets. * @returns {Signal} */ void downloadInfoChanged(DownloadInfoResult info); @@ -186,7 +186,7 @@ signals: /**jsdoc * Triggered when the login status of the user changes. * @function AccountServices.loggedInChanged - * @param {boolean} loggedIn - true if the user is logged in, otherwise false. + * @param {boolean} loggedIn - true if the user is logged in, false if not. * @returns {Signal} * @example Report when your login status changes. * AccountServices.loggedInChanged.connect(function(loggedIn) { diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index fe04ce47ca..45bb15f1a3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -66,32 +66,30 @@ bool Audio::isMuted() const { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getMutedHMD(); - } - else { + } else { return getMutedDesktop(); } } void Audio::setMuted(bool isMuted) { - withWriteLock([&] { - bool isHMD = qApp->isHMDMode(); - if (isHMD) { - setMutedHMD(isMuted); - } - else { - setMutedDesktop(isMuted); - } - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } else { + setMutedDesktop(isMuted); + } } void Audio::setMutedDesktop(bool isMuted) { bool changed = false; - if (_desktopMuted != isMuted) { - changed = true; - _desktopMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit desktopMutedChanged(isMuted); @@ -106,12 +104,14 @@ bool Audio::getMutedDesktop() const { void Audio::setMutedHMD(bool isMuted) { bool changed = false; - if (_hmdMuted != isMuted) { - changed = true; - _hmdMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit hmdMutedChanged(isMuted); @@ -128,12 +128,24 @@ bool Audio::getPTT() { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getPTTHMD(); - } - else { + } else { return getPTTDesktop(); } } +void scripting::Audio::setPushingToTalk(bool pushingToTalk) { + bool changed = false; + withWriteLock([&] { + if (_pushingToTalk != pushingToTalk) { + changed = true; + _pushingToTalk = pushingToTalk; + } + }); + if (changed) { + emit pushingToTalkChanged(pushingToTalk); + } +} + bool Audio::getPushingToTalk() const { return resultWithReadLock([&] { return _pushingToTalk; @@ -144,8 +156,7 @@ void Audio::setPTT(bool enabled) { bool isHMD = qApp->isHMDMode(); if (isHMD) { setPTTHMD(enabled); - } - else { + } else { setPTTDesktop(enabled); } } @@ -156,16 +167,16 @@ void Audio::setPTTDesktop(bool enabled) { if (_pttDesktop != enabled) { changed = true; _pttDesktop = enabled; - if (!enabled) { - // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. - setMutedDesktop(true); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedDesktop(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkDesktopChanged(enabled); @@ -184,16 +195,16 @@ void Audio::setPTTHMD(bool enabled) { if (_pttHMD != enabled) { changed = true; _pttHMD = enabled; - if (!enabled) { - // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. - setMutedHMD(false); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedHMD(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkHMDChanged(enabled); @@ -220,6 +231,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -318,8 +349,7 @@ void Audio::onContextChanged() { }); if (isHMD) { setMuted(getMutedHMD()); - } - else { + } else { setMuted(getMutedDesktop()); } if (changed) { @@ -331,14 +361,10 @@ void Audio::handlePushedToTalk(bool enabled) { if (getPTT()) { if (enabled) { setMuted(false); - } - else { + } else { setMuted(true); } - if (_pushingToTalk != enabled) { - _pushingToTalk = enabled; - emit pushingToTalkChanged(enabled); - } + setPushingToTalk(enabled); } } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 6aa589e399..94f8a7bf54 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -99,6 +99,7 @@ public: bool getMutedHMD() const; void setPTT(bool enabled); bool getPTT(); + void setPushingToTalk(bool pushingToTalk); bool getPushingToTalk() const; // Push-To-Talk setters and getters diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 7482b8be00..3ef9c7953a 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -41,8 +41,8 @@ public: * * @property {WalletScriptingInterface.WalletStatus} walletStatus - The status of the user's wallet. Read-only. * @property {boolean} limitedCommerce - true if Interface is running in limited commerce mode. In limited commerce - * mode, certain Interface functionality is disabled, e.g., users can't buy non-free items from the Marketplace. The Oculus - * Store version of Interface runs in limited commerce mode. Read-only. + * mode, certain Interface functionalities are disabled, e.g., users can't buy items that are not free from the Marketplace. + * The Oculus Store version of Interface runs in limited commerce mode. Read-only. */ class WalletScriptingInterface : public QObject, public Dependency { Q_OBJECT @@ -55,16 +55,16 @@ public: WalletScriptingInterface(); /**jsdoc - * Check and update the user's wallet status. + * Checks and updates the user's wallet status. * @function WalletScriptingInterface.refreshWalletStatus */ Q_INVOKABLE void refreshWalletStatus(); /**jsdoc - * Get the current status of the user's wallet. + * Gets the current status of the user's wallet. * @function WalletScriptingInterface.getWalletStatus * @returns {WalletScriptingInterface.WalletStatus} - * @example Two ways to report your wallet status. + * @example Use two methods to report your wallet's status. * print("Wallet status: " + WalletScriptingInterface.walletStatus); // Same value as next line. * print("Wallet status: " + WalletScriptingInterface.getWalletStatus()); */ @@ -74,11 +74,11 @@ public: * Check that a certified avatar entity is owned by the avatar whose entity it is. The result of the check is provided via * the {@link WalletScriptingInterface.ownershipVerificationSuccess|ownershipVerificationSuccess} and * {@link WalletScriptingInterface.ownershipVerificationFailed|ownershipVerificationFailed} signals.
- * Warning: Neither of these signals fire if the entity is not an avatar entity or it's not a certified - * entity. + * Warning: Neither of these signals are triggered if the entity is not an avatar entity or is not + * certified. * @function WalletScriptingInterface.proveAvatarEntityOwnershipVerification - * @param {Uuid} entityID - The ID of the avatar entity to check. - * @example Check ownership of all nearby certified avatar entities. + * @param {Uuid} entityID - The avatar entity's ID. + * @example Check the ownership of all nearby certified avatar entities. * // Set up response handling. * function ownershipSuccess(entityID) { * print("Ownership test succeeded for: " + entityID); @@ -118,7 +118,7 @@ public: signals: /**jsdoc - * Triggered when the status of the user's wallet changes. + * Triggered when the user's wallet status changes. * @function WalletScriptingInterface.walletStatusChanged * @returns {Signal} * @example Report when your wallet status changes, e.g., when you log in and out. @@ -136,7 +136,7 @@ signals: void limitedCommerceChanged(); /**jsdoc - * Triggered when the user rezzes a certified entity but the user's wallet is not ready and so the certified location of the + * Triggered when the user rezzes a certified entity but the user's wallet is not ready. So the certified location of the * entity cannot be updated in the metaverse. * @function WalletScriptingInterface.walletNotSetup * @returns {Signal} diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 5ae3f7d38e..dfd698f6c5 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -43,14 +43,6 @@ std::unordered_map Overlays::_entityToOverlayTypes; std::unordered_map Overlays::_overlayToEntityTypes; Overlays::Overlays() { - auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Overlays::hoverEnterPointerEvent); - connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, this, &Overlays::hoverOverPointerEvent); - connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Overlays::hoverLeavePointerEvent); - connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Overlays::mousePressPointerEvent); - connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Overlays::mouseMovePointerEvent); - connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Overlays::mouseReleasePointerEvent); - ADD_TYPE_MAP(Box, cube); ADD_TYPE_MAP(Sphere, sphere); _overlayToEntityTypes["rectangle3d"] = "Shape"; @@ -81,13 +73,23 @@ void Overlays::cleanupAllOverlays() { void Overlays::init() { auto entityScriptingInterface = DependencyManager::get().data(); - auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, entityScriptingInterface , &EntityScriptingInterface::hoverEnterEntity); - connect(pointerManager.data(), &PointerManager::hoverContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); - connect(pointerManager.data(), &PointerManager::hoverEndOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); - connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); - connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); - connect(pointerManager.data(), &PointerManager::triggerEndOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); + auto pointerManager = DependencyManager::get().data(); + connect(pointerManager, &PointerManager::hoverBeginOverlay, entityScriptingInterface , &EntityScriptingInterface::hoverEnterEntity); + connect(pointerManager, &PointerManager::hoverContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); + connect(pointerManager, &PointerManager::hoverEndOverlay, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); + connect(pointerManager, &PointerManager::triggerBeginOverlay, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); + connect(pointerManager, &PointerManager::triggerContinueOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); + connect(pointerManager, &PointerManager::triggerEndOverlay, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); + + connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &Overlays::mousePressOnPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOffEntity, this, &Overlays::mousePressOffPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOnEntity, this, &Overlays::mouseDoublePressOnPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseDoublePressOffEntity, this, &Overlays::mouseDoublePressOffPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, &Overlays::mouseReleasePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity, this, &Overlays::mouseMovePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity , this, &Overlays::hoverEnterPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity, this, &Overlays::hoverOverPointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, &Overlays::hoverLeavePointerEvent); } void Overlays::update(float deltatime) { @@ -1159,7 +1161,7 @@ bool Overlays::isAddedOverlay(const QUuid& id) { } void Overlays::sendMousePressOnOverlay(const QUuid& id, const PointerEvent& event) { - mousePressPointerEvent(id, event); + mousePressOnPointerEvent(id, event); } void Overlays::sendMouseReleaseOnOverlay(const QUuid& id, const PointerEvent& event) { @@ -1206,57 +1208,66 @@ float Overlays::height() { return offscreenUi->getWindow()->size().height(); } -static uint32_t toPointerButtons(const QMouseEvent& event) { - uint32_t buttons = 0; - buttons |= event.buttons().testFlag(Qt::LeftButton) ? PointerEvent::PrimaryButton : 0; - buttons |= event.buttons().testFlag(Qt::RightButton) ? PointerEvent::SecondaryButton : 0; - buttons |= event.buttons().testFlag(Qt::MiddleButton) ? PointerEvent::TertiaryButton : 0; - return buttons; -} - -static PointerEvent::Button toPointerButton(const QMouseEvent& event) { - switch (event.button()) { - case Qt::LeftButton: - return PointerEvent::PrimaryButton; - case Qt::RightButton: - return PointerEvent::SecondaryButton; - case Qt::MiddleButton: - return PointerEvent::TertiaryButton; - default: - return PointerEvent::NoButtons; - } -} - -RayToOverlayIntersectionResult getPrevPickResult() { - RayToOverlayIntersectionResult overlayResult; - overlayResult.intersects = false; - auto pickResult = DependencyManager::get()->getPrevPickResultTyped(DependencyManager::get()->getMouseRayPickID()); - if (pickResult) { - overlayResult.intersects = pickResult->type == IntersectionType::LOCAL_ENTITY; - if (overlayResult.intersects) { - overlayResult.intersection = pickResult->intersection; - overlayResult.distance = pickResult->distance; - overlayResult.surfaceNormal = pickResult->surfaceNormal; - overlayResult.overlayID = pickResult->objectID; - overlayResult.extraInfo = pickResult->extraInfo; +void Overlays::mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit mousePressOnOverlay(id, event); } } - return overlayResult; } -PointerEvent Overlays::calculateOverlayPointerEvent(const QUuid& id, const PickRay& ray, - const RayToOverlayIntersectionResult& rayPickResult, QMouseEvent* event, - PointerEvent::EventType eventType) { - glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(id, rayPickResult.intersection); - return PointerEvent(eventType, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, - ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers()); +void Overlays::mousePressOffPointerEvent() { + emit mousePressOffOverlay(); +} + +void Overlays::mouseDoublePressOnPointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit mouseDoublePressOnOverlay(id, event); + } + } +} + +void Overlays::mouseDoublePressOffPointerEvent() { + emit mouseDoublePressOffOverlay(); +} + +void Overlays::mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit mouseReleaseOnOverlay(id, event); + } + } +} + +void Overlays::mouseMovePointerEvent(const QUuid& id, const PointerEvent& event) { + auto keyboard = DependencyManager::get(); + // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed + if (!keyboard->getKeyIDs().contains(id)) { + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit mouseMoveOnOverlay(id, event); + } + } } void Overlays::hoverEnterPointerEvent(const QUuid& id, const PointerEvent& event) { auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { - emit hoverEnterOverlay(id, event); + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit hoverEnterOverlay(id, event); + } } } @@ -1264,7 +1275,10 @@ void Overlays::hoverOverPointerEvent(const QUuid& id, const PointerEvent& event) auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { - emit hoverOverOverlay(id, event); + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit hoverOverOverlay(id, event); + } } } @@ -1272,113 +1286,10 @@ void Overlays::hoverLeavePointerEvent(const QUuid& id, const PointerEvent& event auto keyboard = DependencyManager::get(); // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed if (!keyboard->getKeyIDs().contains(id)) { - emit hoverLeaveOverlay(id, event); - } -} - -std::pair Overlays::mousePressEvent(QMouseEvent* event) { - PerformanceTimer perfTimer("Overlays::mousePressEvent"); - - PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); - if (rayPickResult.intersects) { - _currentClickingOnOverlayID = rayPickResult.overlayID; - - PointerEvent pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press); - mousePressPointerEvent(_currentClickingOnOverlayID, pointerEvent); - return { rayPickResult.distance, rayPickResult.overlayID }; - } - emit mousePressOffOverlay(); - return { FLT_MAX, UNKNOWN_ENTITY_ID }; -} - -void Overlays::mousePressPointerEvent(const QUuid& id, const PointerEvent& event) { - auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeyIDs().contains(id)) { - emit mousePressOnOverlay(id, event); - } -} - -bool Overlays::mouseDoublePressEvent(QMouseEvent* event) { - PerformanceTimer perfTimer("Overlays::mouseDoublePressEvent"); - - PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); - if (rayPickResult.intersects) { - _currentClickingOnOverlayID = rayPickResult.overlayID; - - auto pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press); - emit mouseDoublePressOnOverlay(_currentClickingOnOverlayID, pointerEvent); - return true; - } - emit mouseDoublePressOffOverlay(); - return false; -} - -bool Overlays::mouseReleaseEvent(QMouseEvent* event) { - PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); - - PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); - if (rayPickResult.intersects) { - auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Release); - mouseReleasePointerEvent(rayPickResult.overlayID, pointerEvent); - } - - _currentClickingOnOverlayID = UNKNOWN_ENTITY_ID; - return false; -} - -void Overlays::mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event) { - auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeyIDs().contains(id)) { - emit mouseReleaseOnOverlay(id, event); - } -} - -bool Overlays::mouseMoveEvent(QMouseEvent* event) { - PerformanceTimer perfTimer("Overlays::mouseMoveEvent"); - - PickRay ray = qApp->computePickRay(event->x(), event->y()); - RayToOverlayIntersectionResult rayPickResult = getPrevPickResult(); - if (rayPickResult.intersects) { - auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Move); - mouseMovePointerEvent(rayPickResult.overlayID, pointerEvent); - - // If previously hovering over a different overlay then leave hover on that overlay. - if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID && rayPickResult.overlayID != _currentHoverOverOverlayID) { - auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move); - hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent); + auto entity = DependencyManager::get()->getEntity(id); + if (entity && entity->isLocalEntity()) { + emit hoverLeaveOverlay(id, event); } - - // If hovering over a new overlay then enter hover on that overlay. - if (rayPickResult.overlayID != _currentHoverOverOverlayID) { - hoverEnterPointerEvent(rayPickResult.overlayID, pointerEvent); - } - - // Hover over current overlay. - hoverOverPointerEvent(rayPickResult.overlayID, pointerEvent); - - _currentHoverOverOverlayID = rayPickResult.overlayID; - } else { - // If previously hovering an overlay then leave hover. - if (_currentHoverOverOverlayID != UNKNOWN_ENTITY_ID) { - auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move); - hoverLeavePointerEvent(_currentHoverOverOverlayID, pointerEvent); - - _currentHoverOverOverlayID = UNKNOWN_ENTITY_ID; - } - } - return false; -} - -void Overlays::mouseMovePointerEvent(const QUuid& id, const PointerEvent& event) { - auto keyboard = DependencyManager::get(); - // Do not send keyboard key event to scripts to prevent malignant scripts from gathering what you typed - if (!keyboard->getKeyIDs().contains(id)) { - emit mouseMoveOnOverlay(id, event); } } diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 93efc2bc0b..0b2994b872 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -112,11 +112,6 @@ public: const QVector& discard, bool visibleOnly = false, bool collidableOnly = false); - std::pair mousePressEvent(QMouseEvent* event); - bool mouseDoublePressEvent(QMouseEvent* event); - bool mouseReleaseEvent(QMouseEvent* event); - bool mouseMoveEvent(QMouseEvent* event); - void cleanupAllOverlays(); mutable QScriptEngine _scriptEngine; @@ -719,9 +714,6 @@ private: PointerEvent calculateOverlayPointerEvent(const QUuid& id, const PickRay& ray, const RayToOverlayIntersectionResult& rayPickResult, QMouseEvent* event, PointerEvent::EventType eventType); - QUuid _currentClickingOnOverlayID; - QUuid _currentHoverOverOverlayID; - static QString entityToOverlayType(const QString& type); static QString overlayToEntityType(const QString& type); static std::unordered_map _entityToOverlayTypes; @@ -732,12 +724,17 @@ private: EntityItemProperties convertOverlayToEntityProperties(QVariantMap& overlayProps, std::pair& rotationToSave, const QString& type, bool add, const QUuid& id = QUuid()); private slots: - void mousePressPointerEvent(const QUuid& id, const PointerEvent& event); - void mouseMovePointerEvent(const QUuid& id, const PointerEvent& event); + void mousePressOnPointerEvent(const QUuid& id, const PointerEvent& event); + void mousePressOffPointerEvent(); + void mouseDoublePressOnPointerEvent(const QUuid& id, const PointerEvent& event); + void mouseDoublePressOffPointerEvent(); void mouseReleasePointerEvent(const QUuid& id, const PointerEvent& event); + void mouseMovePointerEvent(const QUuid& id, const PointerEvent& event); void hoverEnterPointerEvent(const QUuid& id, const PointerEvent& event); void hoverOverPointerEvent(const QUuid& id, const PointerEvent& event); void hoverLeavePointerEvent(const QUuid& id, const PointerEvent& event); + + }; #define ADD_TYPE_MAP(entity, overlay) \ diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index e54258fc3e..143c7fa377 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -73,14 +73,14 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; _currentClickingOnEntityID = UNKNOWN_ENTITY_ID; - auto entityScriptingInterface = DependencyManager::get(); + auto entityScriptingInterface = DependencyManager::get().data(); auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginEntity, entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity); - connect(pointerManager.data(), &PointerManager::hoverContinueEntity, entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity); - connect(pointerManager.data(), &PointerManager::hoverEndEntity, entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity); - connect(pointerManager.data(), &PointerManager::triggerBeginEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity); - connect(pointerManager.data(), &PointerManager::triggerContinueEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity); - connect(pointerManager.data(), &PointerManager::triggerEndEntity, entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity); + connect(pointerManager.data(), &PointerManager::hoverBeginEntity, entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity); + connect(pointerManager.data(), &PointerManager::hoverContinueEntity, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity); + connect(pointerManager.data(), &PointerManager::hoverEndEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity); + connect(pointerManager.data(), &PointerManager::triggerBeginEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity); + connect(pointerManager.data(), &PointerManager::triggerContinueEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity); + connect(pointerManager.data(), &PointerManager::triggerEndEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity); // Forward mouse events to web entities auto handlePointerEvent = [&](const QUuid& entityID, const PointerEvent& event) { @@ -93,10 +93,10 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf QMetaObject::invokeMethod(thisEntity.get(), "handlePointerEvent", Q_ARG(const PointerEvent&, event)); } }; - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverEnterEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { + connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, handlePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity, this, handlePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity, this, handlePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { std::shared_ptr thisEntity; auto entity = getEntity(entityID); if (entity && entity->isVisible() && entity->getType() == EntityTypes::Web) { @@ -106,8 +106,8 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf QMetaObject::invokeMethod(thisEntity.get(), "hoverEnterEntity", Q_ARG(const PointerEvent&, event)); } }); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverOverEntity, this, handlePointerEvent); - connect(entityScriptingInterface.data(), &EntityScriptingInterface::hoverLeaveEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { + connect(entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity, this, handlePointerEvent); + connect(entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity, this, [&](const QUuid& entityID, const PointerEvent& event) { std::shared_ptr thisEntity; auto entity = getEntity(entityID); if (entity && entity->isVisible() && entity->getType() == EntityTypes::Web) { @@ -196,8 +196,8 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { }); } -void EntityTreeRenderer::stopNonLocalEntityScripts() { - leaveNonLocalEntities(); +void EntityTreeRenderer::stopDomainAndNonOwnedEntities() { + leaveDomainAndNonOwnedEntities(); // unload and stop the engine if (_entitiesScriptEngine) { QList entitiesWithEntityScripts = _entitiesScriptEngine->getListOfEntityScriptIDs(); @@ -206,7 +206,7 @@ void EntityTreeRenderer::stopNonLocalEntityScripts() { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); if (entityItem) { - if (!entityItem->isLocalEntity()) { + if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { _entitiesScriptEngine->unloadEntityScript(entityID, true); } } @@ -214,8 +214,8 @@ void EntityTreeRenderer::stopNonLocalEntityScripts() { } } -void EntityTreeRenderer::clearNonLocalEntities() { - stopNonLocalEntityScripts(); +void EntityTreeRenderer::clearDomainAndNonOwnedEntities() { + stopDomainAndNonOwnedEntities(); std::unordered_map savedEntities; // remove all entities from the scene @@ -225,7 +225,7 @@ void EntityTreeRenderer::clearNonLocalEntities() { for (const auto& entry : _entitiesInScene) { const auto& renderer = entry.second; const EntityItemPointer& entityItem = renderer->getEntity(); - if (!entityItem->isLocalEntity()) { + if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { renderer->removeFromScene(scene, transaction); } else { savedEntities[entry.first] = entry.second; @@ -239,7 +239,7 @@ void EntityTreeRenderer::clearNonLocalEntities() { _layeredZones.clearNonLocalLayeredZones(); - OctreeProcessor::clearNonLocalEntities(); + OctreeProcessor::clearDomainAndNonOwnedEntities(); } void EntityTreeRenderer::clear() { @@ -655,22 +655,22 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { return didUpdate; } -void EntityTreeRenderer::leaveNonLocalEntities() { +void EntityTreeRenderer::leaveDomainAndNonOwnedEntities() { if (_tree && !_shuttingDown) { - QVector currentLocalEntitiesInside; + QVector currentEntitiesInsideToSave; foreach (const EntityItemID& entityID, _currentEntitiesInside) { EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); - if (!entityItem->isLocalEntity()) { + if (!(entityItem->isLocalEntity() || (entityItem->isAvatarEntity() && entityItem->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { emit leaveEntity(entityID); if (_entitiesScriptEngine) { _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); } } else { - currentLocalEntitiesInside.push_back(entityID); + currentEntitiesInsideToSave.push_back(entityID); } } - _currentEntitiesInside = currentLocalEntitiesInside; + _currentEntitiesInside = currentEntitiesInsideToSave; forceRecheckEntities(); } } @@ -792,11 +792,11 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) { } } -std::pair EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { +QUuid EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { // If we don't have a tree, or we're in the process of shutting down, then don't // process these events. if (!_tree || _shuttingDown) { - return { FLT_MAX, UNKNOWN_ENTITY_ID }; + return UNKNOWN_ENTITY_ID; } PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); @@ -805,11 +805,13 @@ std::pair EntityTreeRenderer::mousePressEvent(QMouseEvent* event) RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); EntityItemPointer entity; if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { - auto properties = entity->getProperties(); - QString urlString = properties.getHref(); - QUrl url = QUrl(urlString, QUrl::StrictMode); - if (url.isValid() && !url.isEmpty()){ - DependencyManager::get()->handleLookupString(urlString); + if (!EntityTree::areEntityClicksCaptured()) { + auto properties = entity->getProperties(); + QString urlString = properties.getHref(); + QUrl url = QUrl(urlString, QUrl::StrictMode); + if (url.isValid() && !url.isEmpty()) { + DependencyManager::get()->handleLookupString(urlString); + } } glm::vec2 pos2D = projectOntoEntityXYPlane(entity, ray, rayPickResult); @@ -827,10 +829,10 @@ std::pair EntityTreeRenderer::mousePressEvent(QMouseEvent* event) _lastPointerEvent = pointerEvent; _lastPointerEventValid = true; - return { rayPickResult.distance, rayPickResult.entityID }; + return rayPickResult.entityID; } emit entityScriptingInterface->mousePressOffEntity(); - return { FLT_MAX, UNKNOWN_ENTITY_ID }; + return UNKNOWN_ENTITY_ID; } void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 51568ab744..a257951ba8 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -87,14 +87,14 @@ public: virtual void init() override; /// clears the tree - virtual void clearNonLocalEntities() override; + virtual void clearDomainAndNonOwnedEntities() override; virtual void clear() override; /// reloads the entity scripts, calling unload and preload void reloadEntityScripts(); // event handles which may generate entity related events - std::pair mousePressEvent(QMouseEvent* event); + QUuid mousePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); void mouseDoublePressEvent(QMouseEvent* event); void mouseMoveEvent(QMouseEvent* event); @@ -170,7 +170,7 @@ private: bool findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar = nullptr); bool applyLayeredZones(); - void stopNonLocalEntityScripts(); + void stopDomainAndNonOwnedEntities(); void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false); @@ -179,7 +179,7 @@ private: QScriptValueList createEntityArgs(const EntityItemID& entityID); bool checkEnterLeaveEntities(); - void leaveNonLocalEntities(); + void leaveDomainAndNonOwnedEntities(); void leaveAllEntities(); void forceRecheckEntities(); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 6e404ce690..d4f15fa8b2 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -78,13 +78,14 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { return std::static_pointer_cast(newElement); } -void EntityTree::eraseNonLocalEntities() { +void EntityTree::eraseDomainAndNonOwnedEntities() { emit clearingEntities(); if (_simulation) { // local entities are not in the simulation, so we clear ALL _simulation->clearEntities(); } + this->withWriteLock([&] { QHash savedEntities; // NOTE: lock the Tree first, then lock the _entityMap. @@ -93,10 +94,10 @@ void EntityTree::eraseNonLocalEntities() { foreach(EntityItemPointer entity, _entityMap) { EntityTreeElementPointer element = entity->getElement(); if (element) { - element->cleanupNonLocalEntities(); + element->cleanupDomainAndNonOwnedEntities(); } - if (entity->isLocalEntity()) { + if (entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getMyAvatarSessionUUID())) { savedEntities[entity->getEntityItemID()] = entity; } else { int32_t spaceIndex = entity->getSpaceIndex(); @@ -114,15 +115,16 @@ void EntityTree::eraseNonLocalEntities() { { QWriteLocker locker(&_needsParentFixupLock); - QVector localEntitiesNeedsParentFixup; + QVector needParentFixup; foreach (EntityItemWeakPointer entityItem, _needsParentFixup) { - if (!entityItem.expired() && entityItem.lock()->isLocalEntity()) { - localEntitiesNeedsParentFixup.push_back(entityItem); + auto entity = entityItem.lock(); + if (entity && (entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getMyAvatarSessionUUID()))) { + needParentFixup.push_back(entityItem); } } - _needsParentFixup = localEntitiesNeedsParentFixup; + _needsParentFixup = needParentFixup; } } @@ -2972,6 +2974,7 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const { std::function EntityTree::_getEntityObjectOperator = nullptr; std::function EntityTree::_textSizeOperator = nullptr; +std::function EntityTree::_areEntityClicksCapturedOperator = nullptr; QObject* EntityTree::getEntityObject(const QUuid& id) { if (_getEntityObjectOperator) { @@ -2987,6 +2990,13 @@ QSizeF EntityTree::textSize(const QUuid& id, const QString& text) { return QSizeF(0.0f, 0.0f); } +bool EntityTree::areEntityClicksCaptured() { + if (_areEntityClicksCapturedOperator) { + return _areEntityClicksCapturedOperator(); + } + return false; +} + void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, MovingEntitiesOperator& moveOperator, bool force, bool tellServer) { // if the queryBox has changed, tell the entity-server diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index dcce0e4b99..39b3dc57c7 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -75,7 +75,7 @@ public: } - virtual void eraseNonLocalEntities() override; + virtual void eraseDomainAndNonOwnedEntities() override; virtual void eraseAllOctreeElements(bool createNewRoot = true) override; virtual void readBitstreamToTree(const unsigned char* bitstream, @@ -255,6 +255,7 @@ public: QByteArray computeNonce(const QString& certID, const QString ownerKey); bool verifyNonce(const QString& certID, const QString& nonce, EntityItemID& id); + QUuid getMyAvatarSessionUUID() { return _myAvatar ? _myAvatar->getSessionUUID() : QUuid(); } void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } void swapStaleProxies(std::vector& proxies) { proxies.swap(_staleProxies); } @@ -268,6 +269,9 @@ public: static void setTextSizeOperator(std::function textSizeOperator) { _textSizeOperator = textSizeOperator; } static QSizeF textSize(const QUuid& id, const QString& text); + static void setEntityClicksCapturedOperator(std::function areEntityClicksCapturedOperator) { _areEntityClicksCapturedOperator = areEntityClicksCapturedOperator; } + static bool areEntityClicksCaptured(); + std::map getNamedPaths() const { return _namedPaths; } void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, @@ -378,6 +382,7 @@ private: static std::function _getEntityObjectOperator; static std::function _textSizeOperator; + static std::function _areEntityClicksCapturedOperator; std::vector _staleProxies; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index ce6f20262f..aab98adb52 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -697,11 +697,11 @@ EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemI return foundEntity; } -void EntityTreeElement::cleanupNonLocalEntities() { +void EntityTreeElement::cleanupDomainAndNonOwnedEntities() { withWriteLock([&] { EntityItems savedEntities; foreach(EntityItemPointer entity, _entityItems) { - if (!entity->isLocalEntity()) { + if (!(entity->isLocalEntity() || (entity->isAvatarEntity() && entity->getOwningAvatarID() == getTree()->getMyAvatarSessionUUID()))) { entity->preDelete(); entity->_element = NULL; } else { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index f82eaa7fb1..f94da44138 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -190,7 +190,7 @@ public: EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); - void cleanupNonLocalEntities(); + void cleanupDomainAndNonOwnedEntities(); void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities bool removeEntityItem(EntityItemPointer entity, bool deletion = false); diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 0efbbbb16c..08468617ba 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -164,7 +164,7 @@ protected: int _lastKnownCurrentFrame{-1}; glm::u8vec3 _color; - glm::vec3 _modelScale; + glm::vec3 _modelScale { 1.0f }; QString _modelURL; bool _relayParentJoints; bool _groupCulled { false }; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 9e7f422b40..5246242a1e 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -167,7 +167,6 @@ glm::mat4 getGlobalTransform(const QMultiMap& _connectionParen } } } - return globalTransform; } @@ -436,6 +435,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr hfmModel.originalURL = url; float unitScaleFactor = 1.0f; + glm::quat upAxisZRotation; + bool applyUpAxisZRotation = false; glm::vec3 ambientColor; QString hifiGlobalNodeID; unsigned int meshIndex = 0; @@ -473,11 +474,22 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr if (subobject.name == propertyName) { static const QVariant UNIT_SCALE_FACTOR = QByteArray("UnitScaleFactor"); static const QVariant AMBIENT_COLOR = QByteArray("AmbientColor"); + static const QVariant UP_AXIS = QByteArray("UpAxis"); const auto& subpropName = subobject.properties.at(0); if (subpropName == UNIT_SCALE_FACTOR) { unitScaleFactor = subobject.properties.at(index).toFloat(); } else if (subpropName == AMBIENT_COLOR) { ambientColor = getVec3(subobject.properties, index); + } else if (subpropName == UP_AXIS) { + constexpr int UP_AXIS_Y = 1; + constexpr int UP_AXIS_Z = 2; + int upAxis = subobject.properties.at(index).toInt(); + if (upAxis == UP_AXIS_Y) { + // No update necessary, y up is the default + } else if (upAxis == UP_AXIS_Z) { + upAxisZRotation = glm::angleAxis(glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); + applyUpAxisZRotation = true; + } } } } @@ -1269,9 +1281,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr joint.geometricScaling = fbxModel.geometricScaling; joint.isSkeletonJoint = fbxModel.isLimbNode; hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint); - + if (applyUpAxisZRotation && joint.parentIndex == -1) { + joint.rotation *= upAxisZRotation; + joint.translation = upAxisZRotation * joint.translation; + } glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; - if (joint.parentIndex == -1) { joint.transform = hfmModel.offset * glm::translate(joint.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; @@ -1664,6 +1678,14 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } + if (applyUpAxisZRotation) { + hfmModelPtr->meshExtents.transform(glm::mat4_cast(upAxisZRotation)); + hfmModelPtr->bindExtents.transform(glm::mat4_cast(upAxisZRotation)); + for (auto &mesh : hfmModelPtr->meshes) { + mesh.modelTransform *= glm::mat4_cast(upAxisZRotation); + mesh.meshExtents.transform(glm::mat4_cast(upAxisZRotation)); + } + } return hfmModelPtr; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index aac29201f1..82076f618b 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -149,7 +149,7 @@ public: OctreeElementPointer getRoot() { return _rootElement; } - virtual void eraseNonLocalEntities() { _isDirty = true; }; + virtual void eraseDomainAndNonOwnedEntities() { _isDirty = true; }; virtual void eraseAllOctreeElements(bool createNewRoot = true); virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index 18c8630391..03c8b9ca2f 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -198,10 +198,10 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe } -void OctreeProcessor::clearNonLocalEntities() { +void OctreeProcessor::clearDomainAndNonOwnedEntities() { if (_tree) { _tree->withWriteLock([&] { - _tree->eraseNonLocalEntities(); + _tree->eraseDomainAndNonOwnedEntities(); }); } } diff --git a/libraries/octree/src/OctreeProcessor.h b/libraries/octree/src/OctreeProcessor.h index bc5618e657..40af7a39f8 100644 --- a/libraries/octree/src/OctreeProcessor.h +++ b/libraries/octree/src/OctreeProcessor.h @@ -43,7 +43,7 @@ public: virtual void init(); /// clears the tree - virtual void clearNonLocalEntities(); + virtual void clearDomainAndNonOwnedEntities(); virtual void clear(); float getAverageElementsPerPacket() const { return _elementsPerPacket.getAverage(); } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9bb3f72b31..dd9b0280ca 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1346,14 +1346,19 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { } void Model::computeMeshPartLocalBounds() { - for (auto& part : _modelMeshRenderItems) { - const Model::MeshState& state = _meshStates.at(part->_meshIndex); - if (_useDualQuaternionSkinning) { - part->computeAdjustedLocalBound(state.clusterDualQuaternions); - } else { - part->computeAdjustedLocalBound(state.clusterMatrices); - } + render::Transaction transaction; + auto meshStates = _meshStates; + for (auto renderItem : _modelMeshRenderItemIDs) { + transaction.updateItem(renderItem, [this, meshStates](ModelMeshPartPayload& data) { + const Model::MeshState& state = meshStates.at(data._meshIndex); + if (_useDualQuaternionSkinning) { + data.computeAdjustedLocalBound(state.clusterDualQuaternions); + } else { + data.computeAdjustedLocalBound(state.clusterMatrices); + } + }); } + AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); } // virtual diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 5d33a6a061..825017b1fe 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -976,7 +976,9 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& using PointerHandler = std::function; auto makePointerHandler = [this](QString eventName) -> PointerHandler { return [this, eventName](const EntityItemID& entityItemID, const PointerEvent& event) { - forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) }); + if (!EntityTree::areEntityClicksCaptured()) { + forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) }); + } }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 51d070d8cd..bf44cfa7cc 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -26,12 +26,15 @@ var UNMUTE_ICONS = { icon: "icons/tablet-icons/mic-unmute-i.svg", activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; function onMuteToggled() { if (Audio.pushingToTalk) { - return; - } - if (Audio.muted) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { button.editProperties(UNMUTE_ICONS); @@ -71,6 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); +Audio.pushingToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -79,6 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushingToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 72ec0ea29ad70609972b7e5657aadb96f5614b6a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Mar 2019 11:16:03 -0800 Subject: [PATCH 040/132] Update Oculus Mobile SDK to latest version --- cmake/macros/TargetOculusMobile.cmake | 2 +- hifi_android.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmake/macros/TargetOculusMobile.cmake b/cmake/macros/TargetOculusMobile.cmake index 3eaa008b14..f5229845a9 100644 --- a/cmake/macros/TargetOculusMobile.cmake +++ b/cmake/macros/TargetOculusMobile.cmake @@ -1,6 +1,6 @@ macro(target_oculus_mobile) - set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus/VrApi) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus_1.22/VrApi) # Mobile SDK set(OVR_MOBILE_INCLUDE_DIRS ${INSTALL_DIR}/Include) diff --git a/hifi_android.py b/hifi_android.py index b8a606a82f..42b472e960 100644 --- a/hifi_android.py +++ b/hifi_android.py @@ -45,10 +45,10 @@ ANDROID_PACKAGES = { 'sharedLibFolder': 'lib', 'includeLibs': ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'] }, - 'oculus': { - 'file': 'ovr_sdk_mobile_1.19.0.zip', - 'versionId': 's_RN1vlEvUi3pnT7WPxUC4pQ0RJBs27y', - 'checksum': '98f0afb62861f1f02dd8110b31ed30eb', + 'oculus_1.22': { + 'file': 'ovr_sdk_mobile_1.22.zip', + 'versionId': 'InhomR5gwkzyiLAelH3X9k4nvV3iIpA_', + 'checksum': '1ac3c5b0521e5406f287f351015daff8', 'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release', 'includeLibs': ['libvrapi.so'] }, From 4467567ee6b6dd478f3baef76dc8e089b760fba8 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:18:57 -0800 Subject: [PATCH 041/132] changing text display --- interface/resources/qml/hifi/audio/MicBar.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 2ab1085408..9d1cbfbc6c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; + visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -155,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } From a318b715c3c12ab66cf8c977479c7b6ae1aaadde Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:45:43 -0800 Subject: [PATCH 042/132] exposing setting pushingToTalk --- interface/src/scripting/Audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 94f8a7bf54..9ad4aac9c1 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -72,7 +72,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) - Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk WRITE setPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; From 05ba515c73491ebc9660e34bbb69bfed49ad10a5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Mar 2019 13:13:45 -0800 Subject: [PATCH 043/132] Support KHR_no_error in the VR context --- libraries/oculusMobile/src/ovr/GLContext.cpp | 11 +++++------ libraries/oculusMobile/src/ovr/GLContext.h | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/libraries/oculusMobile/src/ovr/GLContext.cpp b/libraries/oculusMobile/src/ovr/GLContext.cpp index 449ba67084..8d81df42e5 100644 --- a/libraries/oculusMobile/src/ovr/GLContext.cpp +++ b/libraries/oculusMobile/src/ovr/GLContext.cpp @@ -13,10 +13,7 @@ #include #include - -#if !defined(EGL_OPENGL_ES3_BIT_KHR) -#define EGL_OPENGL_ES3_BIT_KHR 0x0040 -#endif +#include using namespace ovr; @@ -129,7 +126,7 @@ void GLContext::doneCurrent() { eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } -bool GLContext::create(EGLDisplay display, EGLContext shareContext) { +bool GLContext::create(EGLDisplay display, EGLContext shareContext, bool noError) { this->display = display; auto config = findConfig(display); @@ -139,7 +136,9 @@ bool GLContext::create(EGLDisplay display, EGLContext shareContext) { return false; } - EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }; + EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, + noError ? EGL_CONTEXT_OPENGL_NO_ERROR_KHR : EGL_NONE, EGL_TRUE, + EGL_NONE }; context = eglCreateContext(display, config, shareContext, contextAttribs); if (context == EGL_NO_CONTEXT) { diff --git a/libraries/oculusMobile/src/ovr/GLContext.h b/libraries/oculusMobile/src/ovr/GLContext.h index 04f96e8d47..5ccbf20c42 100644 --- a/libraries/oculusMobile/src/ovr/GLContext.h +++ b/libraries/oculusMobile/src/ovr/GLContext.h @@ -25,7 +25,7 @@ struct GLContext { static EGLConfig findConfig(EGLDisplay display); bool makeCurrent(); void doneCurrent(); - bool create(EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY), EGLContext shareContext = EGL_NO_CONTEXT); + bool create(EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY), EGLContext shareContext = EGL_NO_CONTEXT, bool noError = false); void destroy(); operator bool() const { return context != EGL_NO_CONTEXT; } static void initModule(); From b515a0cceb3633d4872911cf10ba5cf274279df1 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Mar 2019 13:15:12 -0800 Subject: [PATCH 044/132] Support KHR_no_error in the VR wrapper API --- libraries/oculusMobile/src/ovr/VrHandler.cpp | 25 +++++++++++++------- libraries/oculusMobile/src/ovr/VrHandler.h | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libraries/oculusMobile/src/ovr/VrHandler.cpp b/libraries/oculusMobile/src/ovr/VrHandler.cpp index 3fe3901517..6cc2ec9526 100644 --- a/libraries/oculusMobile/src/ovr/VrHandler.cpp +++ b/libraries/oculusMobile/src/ovr/VrHandler.cpp @@ -42,6 +42,8 @@ struct VrSurface : public TaskQueue { uint32_t readFbo{0}; std::atomic presentIndex{1}; double displayTime{0}; + // Not currently set by anything + bool noErrorContext { false }; static constexpr float EYE_BUFFER_SCALE = 1.0f; @@ -71,7 +73,7 @@ struct VrSurface : public TaskQueue { EGLContext currentContext = eglGetCurrentContext(); EGLDisplay currentDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); - vrglContext.create(currentDisplay, currentContext); + vrglContext.create(currentDisplay, currentContext, noErrorContext); vrglContext.makeCurrent(); glm::uvec2 eyeTargetSize; @@ -91,14 +93,17 @@ struct VrSurface : public TaskQueue { } void shutdown() { + noErrorContext = false; + // Destroy the context? } - void setHandler(VrHandler *newHandler) { + void setHandler(VrHandler *newHandler, bool noError) { withLock([&] { isRenderThread = newHandler != nullptr; if (handler != newHandler) { shutdown(); handler = newHandler; + noErrorContext = noError; init(); if (handler) { updateVrMode(); @@ -142,7 +147,6 @@ struct VrSurface : public TaskQueue { __android_log_write(ANDROID_LOG_WARN, "QQQ_OVR", "vrapi_LeaveVrMode"); vrapi_SetClockLevels(session, 1, 1); vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_OFF); - vrapi_SetDisplayRefreshRate(session, 60); vrapi_LeaveVrMode(session); session = nullptr; @@ -153,16 +157,19 @@ struct VrSurface : public TaskQueue { ovrJava java{ vm, env, oculusActivity }; ovrModeParms modeParms = vrapi_DefaultModeParms(&java); modeParms.Flags |= VRAPI_MODE_FLAG_NATIVE_WINDOW; + if (noErrorContext) { + modeParms.Flags |= VRAPI_MODE_FLAG_CREATE_CONTEXT_NO_ERROR; + } modeParms.Display = (unsigned long long) vrglContext.display; modeParms.ShareContext = (unsigned long long) vrglContext.context; modeParms.WindowSurface = (unsigned long long) nativeWindow; session = vrapi_EnterVrMode(&modeParms); - ovrPosef trackingTransform = vrapi_GetTrackingTransform( session, VRAPI_TRACKING_TRANSFORM_SYSTEM_CENTER_EYE_LEVEL); - vrapi_SetTrackingTransform( session, trackingTransform ); - vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, pthread_self()); + vrapi_SetTrackingSpace( session, VRAPI_TRACKING_SPACE_LOCAL); + vrapi_SetPerfThread(session, VRAPI_PERF_THREAD_TYPE_RENDERER, gettid()); vrapi_SetClockLevels(session, 2, 4); vrapi_SetExtraLatencyMode(session, VRAPI_EXTRA_LATENCY_MODE_DYNAMIC); - vrapi_SetDisplayRefreshRate(session, 72); + // Generates a warning on the quest: "vrapi_SetDisplayRefreshRate: Dynamic Display Refresh Rate not supported" + // vrapi_SetDisplayRefreshRate(session, 72); }); } } @@ -227,8 +234,8 @@ bool VrHandler::vrActive() const { return SURFACE.session != nullptr; } -void VrHandler::setHandler(VrHandler* handler) { - SURFACE.setHandler(handler); +void VrHandler::setHandler(VrHandler* handler, bool noError) { + SURFACE.setHandler(handler, noError); } void VrHandler::pollTask() { diff --git a/libraries/oculusMobile/src/ovr/VrHandler.h b/libraries/oculusMobile/src/ovr/VrHandler.h index 3c36ee8626..46e99f7476 100644 --- a/libraries/oculusMobile/src/ovr/VrHandler.h +++ b/libraries/oculusMobile/src/ovr/VrHandler.h @@ -21,7 +21,7 @@ public: using HandlerTask = std::function; using OvrMobileTask = std::function; using OvrJavaTask = std::function; - static void setHandler(VrHandler* handler); + static void setHandler(VrHandler* handler, bool noError = false); static bool withOvrMobile(const OvrMobileTask& task); protected: From de1513a6dd57d979d67192f8a39c5fc8ee50bbd5 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 6 Mar 2019 13:22:48 -0800 Subject: [PATCH 045/132] Turn on KHR_no_error for the quest frame player --- android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp index 78a4487284..d5b87af7fa 100644 --- a/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp +++ b/android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp @@ -117,7 +117,8 @@ void RenderThread::setup() { { std::unique_lock lock(_frameLock); } ovr::VrHandler::initVr(); - ovr::VrHandler::setHandler(this); + // Enable KHR_no_error for this context + ovr::VrHandler::setHandler(this, true); makeCurrent(); From cea17985ffdfdf736939d4adcda89eb7a4e147a2 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 6 Mar 2019 13:55:20 -0800 Subject: [PATCH 046/132] new jointRotationOffset field --- .../Assets/Editor/AvatarExporter.cs | 13 ++++--------- tools/unity-avatar-exporter/Assets/README.txt | 2 +- .../avatarExporter.unitypackage | Bin 13667 -> 13634 bytes 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 7b90145223..dcda499387 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -14,7 +14,7 @@ using System.Collections.Generic; class AvatarExporter : MonoBehaviour { // update version number for every PR that changes this file, also set updated version in README file - static readonly string AVATAR_EXPORTER_VERSION = "0.2"; + static readonly string AVATAR_EXPORTER_VERSION = "0.3.2"; static readonly float HIPS_GROUND_MIN_Y = 0.01f; static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; @@ -551,13 +551,10 @@ class AvatarExporter : MonoBehaviour { // generate joint rotation offsets for both humanoid-mapped bones as well as extra unmapped bones Quaternion jointOffset = new Quaternion(); - string outputJointName = ""; if (userBoneInfo.HasHumanMapping()) { - outputJointName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneInfo.humanName]; Quaternion rotation = REFERENCE_ROTATIONS[userBoneInfo.humanName]; jointOffset = Quaternion.Inverse(userBoneInfo.rotation) * rotation; } else { - outputJointName = userBoneName; jointOffset = Quaternion.Inverse(userBoneInfo.rotation); string lastRequiredParent = FindLastRequiredAncestorBone(userBoneName); if (lastRequiredParent != "root") { @@ -569,11 +566,9 @@ class AvatarExporter : MonoBehaviour { } // swap from left-handed (Unity) to right-handed (HiFi) coordinates and write out joint rotation offset to fst - if (!string.IsNullOrEmpty(outputJointName)) { - jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); - File.AppendAllText(exportFstPath, "jointRotationOffset = " + outputJointName + " = (" + jointOffset.x + ", " + - jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); - } + jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); + File.AppendAllText(exportFstPath, "jointRotationOffset2 = " + userBoneName + " = (" + jointOffset.x + ", " + + jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); } // open File Explorer to the project directory once finished diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index c84cec2978..9aea30dc5b 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,6 +1,6 @@ High Fidelity, Inc. Avatar Exporter -Version 0.2 +Version 0.3.2 Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index 95c000e7c61b7623e0da0a1f9974e0744375014e..ce4ed346f878123160f51e7584f34097ac63e3e1 100644 GIT binary patch delta 12995 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 P7ec=PP;I)t01yEHI%jNC From e218e4bead36d28fa0e4d7de630f7acd72ec1851 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 17:29:32 -0800 Subject: [PATCH 047/132] updating compile failure + icons/settings update --- .../icons/tablet-icons/mic-ptt-a.svg | 1 + .../icons/tablet-icons/mic-ptt-i.svg | 24 +++++++ interface/resources/qml/hifi/audio/Audio.qml | 64 ++++++++++++++++--- interface/resources/qml/hifi/audio/MicBar.qml | 8 ++- interface/src/scripting/Audio.cpp | 20 ------ scripts/system/audio.js | 14 ++-- 6 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-a.svg create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-i.svg diff --git a/interface/resources/icons/tablet-icons/mic-ptt-a.svg b/interface/resources/icons/tablet-icons/mic-ptt-a.svg new file mode 100644 index 0000000000..e6df3c69d7 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-a.svg @@ -0,0 +1 @@ +mic-ptt-a \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/mic-ptt-i.svg b/interface/resources/icons/tablet-icons/mic-ptt-i.svg new file mode 100644 index 0000000000..2141ea5229 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-i.svg @@ -0,0 +1,24 @@ + + + + + + diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 45358f59a2..d44a9c862e 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -120,6 +120,10 @@ Rectangle { isRedCheck: true; checked: AudioScriptingInterface.muted; onClicked: { + if (AudioScriptingInterface.pushToTalk && !checked) { + // disable push to talk if unmuting + AudioScriptingInterface.pushToTalk = false; + } AudioScriptingInterface.muted = checked; checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } @@ -150,7 +154,23 @@ Rectangle { } AudioControls.CheckBox { spacing: muteMic.spacing - text: qsTr("Push To Talk"); + text: qsTr("Show audio level meter"); + checked: AvatarInputs.showAudioTools; + onClicked: { + AvatarInputs.showAudioTools = checked; + checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + } + onXChanged: rightMostInputLevelPos = x + width + } + } + + Separator {} + + ColumnLayout { + spacing: muteMic.spacing; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { if (isVR) { @@ -167,15 +187,41 @@ Rectangle { }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); - checked: AvatarInputs.showAudioTools; - onClicked: { - AvatarInputs.showAudioTools = checked; - checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + Item { + id: pttTextContainer + x: margins.paddings; + width: rightMostInputLevelPos + height: pttTextMetrics.height + visible: true + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + wrapMode: Text.WordWrap + color: hifi.colors.white; + width: parent.width; + font.italic: true + size: 16; + text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : + qsTr("Press and hold the button \"T\" to unmute."); + onTextChanged: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } + } + } + Component.onCompleted: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } } - onXChanged: rightMostInputLevelPos = x + width } } } diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 9d1cbfbc6c..50477b82f8 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -67,6 +67,9 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { + if (AudioScriptingInterface.pushToTalk) { + return; + } AudioScriptingInterface.muted = !AudioScriptingInterface.muted; Tablet.playSound(TabletEnums.ButtonClick); } @@ -109,9 +112,10 @@ Rectangle { Image { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; id: image; - source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; width: 30; height: 30; @@ -155,7 +159,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED PTT-(T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 45bb15f1a3..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,26 +231,6 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index bf44cfa7cc..19ed3faef2 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -27,12 +27,12 @@ var UNMUTE_ICONS = { activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; var PTT_ICONS = { - icon: "icons/tablet-icons/mic-unmute-i.svg", - activeIcon: "icons/tablet-icons/mic-unmute-a.svg" + icon: "icons/tablet-icons/mic-ptt-i.svg", + activeIcon: "icons/tablet-icons/mic-ptt-a.svg" }; function onMuteToggled() { - if (Audio.pushingToTalk) { + if (Audio.pushToTalk) { button.editProperties(PTT_ICONS); } else if (Audio.muted) { button.editProperties(MUTE_ICONS); @@ -63,8 +63,8 @@ function onScreenChanged(type, url) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ - icon: Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, - activeIcon: Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, + icon: Audio.pushToTalk ? PTT_ICONS.icon : Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, + activeIcon: Audio.pushToTalk ? PTT_ICONS.activeIcon : Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, text: TABLET_BUTTON_NAME, sortOrder: 1 }); @@ -74,7 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); -Audio.pushingToTalkChanged.connect(onMuteToggled); +Audio.pushToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -83,7 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); - Audio.pushingToTalkChanged.disconnect(onMuteToggled); + Audio.pushToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 36f62f05d1b8d96199419aacab89c204bee6a58d Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:28 -0800 Subject: [PATCH 048/132] Add pushToTalk.js controllerModule. --- .../controllerModules/pushToTalk.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/system/controllers/controllerModules/pushToTalk.js diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js new file mode 100644 index 0000000000..e764b228c9 --- /dev/null +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -0,0 +1,75 @@ +"use strict"; + +// Created by Jason C. Najera on 3/7/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Handles Push-to-Talk functionality for HMD mode. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { // BEGIN LOCAL_SCOPE + function PushToTalkHandler() { + var _this = this; + this.active = false; + //var pttMapping, mappingName; + + this.setup = function() { + //mappingName = 'Hifi-PTT-Dev-' + Math.random(); + //pttMapping = Controller.newMapping(mappingName); + //pttMapping.enable(); + }; + + this.shouldTalk = function (controllerData) { + // Set up test against controllerData here... + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? true : false; + }; + + this.shouldStopTalking = function (controllerData) { + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? false : true; + }; + + this.isReady = function (controllerData, deltaTime) { + if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + Audio.pushingToTalk = true; + returnMakeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { + Audio.pushingToTalk = false; + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + //pttMapping.disable(); + }; + + this.parameters = makeDispatcherModuleParameters( + 950, + ["head"], + [], + 100); + } + + var pushToTalk = new PushToTalkHandler(); + enableDispatcherModule("PushToTalk", pushToTalk); + + function cleanup () { + pushToTalk.cleanup(); + disableDispatcherModule("PushToTalk"); + }; + + Script.scriptEnding.connect(cleanup); +}()); // END LOCAL_SCOPE \ No newline at end of file From f444f383eba17e47a39032411ce08b33d6df2b57 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:48 -0800 Subject: [PATCH 049/132] Enable pushToTalk.js controller module. --- scripts/system/controllers/controllerDispatcher.js | 1 + scripts/system/controllers/controllerScripts.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 28c3e2a299..f4c0cbb0d6 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -58,6 +58,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. this.activitySlots = { + head: false, leftHand: false, rightHand: false, rightHandTrigger: false, diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 86ff7701c3..d236d6b12a 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -34,7 +34,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearGrabHyperLinkEntity.js", "controllerModules/nearTabletHighlight.js", "controllerModules/nearGrabEntity.js", - "controllerModules/farGrabEntity.js" + "controllerModules/farGrabEntity.js", + "controllerModules/pushToTalk.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From d8478e0b17d56500ff1fd4debdd322fbeec77a4c Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 050/132] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++ interface/resources/qml/hifi/audio/MicBar.qml | 9 +- interface/src/Application.cpp | 18 ++ interface/src/Application.h | 2 + interface/src/scripting/Audio.cpp | 174 +++++++++++++++++- interface/src/scripting/Audio.h | 89 ++++++++- scripts/system/audio.js | 3 + 7 files changed, 302 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..45358f59a2 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -148,6 +148,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 39f75a9182..f91058bc3c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -11,10 +11,13 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + HifiConstants { id: hifi; } + readonly property var level: AudioScriptingInterface.inputLevel; property bool gated: false; @@ -131,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.muted; + visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -152,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.muted ? "MUTED" : "MUTE"; + text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8ab4d10db..fa63757560 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1435,6 +1435,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::pushedToTalk, + reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -4199,6 +4201,10 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_T: + emit pushedToTalk(true); + break; + case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4304,6 +4310,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } + + switch (event->key()) { + case Qt::Key_T: + emit pushedToTalk(false); + break; + } } void Application::focusOutEvent(QFocusEvent* event) { @@ -5235,6 +5247,9 @@ void Application::loadSettings() { } } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->loadData(); + getMyAvatar()->loadData(); _settingsLoaded = true; } @@ -5244,6 +5259,9 @@ void Application::saveSettings() const { DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->saveData(); + Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a8cc9450c5..1c86326f90 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,6 +358,8 @@ signals: void miniTabletEnabledChanged(bool enabled); + void pushedToTalk(bool enabled); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..fe04ce47ca 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -63,26 +63,163 @@ void Audio::stopRecording() { } bool Audio::isMuted() const { - return resultWithReadLock([&] { - return _isMuted; - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getMutedHMD(); + } + else { + return getMutedDesktop(); + } } void Audio::setMuted(bool isMuted) { + withWriteLock([&] { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } + else { + setMutedDesktop(isMuted); + } + }); +} + +void Audio::setMutedDesktop(bool isMuted) { + bool changed = false; + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit desktopMutedChanged(isMuted); + } +} + +bool Audio::getMutedDesktop() const { + return resultWithReadLock([&] { + return _desktopMuted; + }); +} + +void Audio::setMutedHMD(bool isMuted) { + bool changed = false; + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit hmdMutedChanged(isMuted); + } +} + +bool Audio::getMutedHMD() const { + return resultWithReadLock([&] { + return _hmdMuted; + }); +} + +bool Audio::getPTT() { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getPTTHMD(); + } + else { + return getPTTDesktop(); + } +} + +bool Audio::getPushingToTalk() const { + return resultWithReadLock([&] { + return _pushingToTalk; + }); +} + +void Audio::setPTT(bool enabled) { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setPTTHMD(enabled); + } + else { + setPTTDesktop(enabled); + } +} + +void Audio::setPTTDesktop(bool enabled) { bool changed = false; withWriteLock([&] { - if (_isMuted != isMuted) { - _isMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (_pttDesktop != enabled) { changed = true; + _pttDesktop = enabled; + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } } }); if (changed) { - emit mutedChanged(isMuted); + emit pushToTalkChanged(enabled); + emit pushToTalkDesktopChanged(enabled); } } +bool Audio::getPTTDesktop() const { + return resultWithReadLock([&] { + return _pttDesktop; + }); +} + +void Audio::setPTTHMD(bool enabled) { + bool changed = false; + withWriteLock([&] { + if (_pttHMD != enabled) { + changed = true; + _pttHMD = enabled; + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + } + }); + if (changed) { + emit pushToTalkChanged(enabled); + emit pushToTalkHMDChanged(enabled); + } +} + +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -179,11 +316,32 @@ void Audio::onContextChanged() { changed = true; } }); + if (isHMD) { + setMuted(getMutedHMD()); + } + else { + setMuted(getMutedDesktop()); + } if (changed) { emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); } } +void Audio::handlePushedToTalk(bool enabled) { + if (getPTT()) { + if (enabled) { + setMuted(false); + } + else { + setMuted(true); + } + if (_pushingToTalk != enabled) { + _pushingToTalk = enabled; + emit pushingToTalkChanged(enabled); + } + } +} + void Audio::setReverb(bool enable) { withWriteLock([&] { DependencyManager::get()->setReverb(enable); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..6aa589e399 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -12,6 +12,7 @@ #ifndef hifi_scripting_Audio_h #define hifi_scripting_Audio_h +#include #include "AudioScriptingInterface.h" #include "AudioDevices.h" #include "AudioEffectOptions.h" @@ -19,6 +20,9 @@ #include "AudioFileWav.h" #include +using MutedGetter = std::function; +using MutedSetter = std::function; + namespace scripting { class Audio : public AudioScriptingInterface, protected ReadWriteLockable { @@ -63,6 +67,12 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) + Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) + Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); + Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) + Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; @@ -82,6 +92,25 @@ public: void showMicMeter(bool show); + // Mute setting setters and getters + void setMutedDesktop(bool isMuted); + bool getMutedDesktop() const; + void setMutedHMD(bool isMuted); + bool getMutedHMD() const; + void setPTT(bool enabled); + bool getPTT(); + bool getPushingToTalk() const; + + // Push-To-Talk setters and getters + void setPTTDesktop(bool enabled); + bool getPTTDesktop() const; + void setPTTHMD(bool enabled); + bool getPTTHMD() const; + + // Settings handlers + void saveData(); + void loadData(); + /**jsdoc * @function Audio.setInputDevice * @param {object} device @@ -193,6 +222,46 @@ signals: */ void mutedChanged(bool isMuted); + /**jsdoc + * Triggered when desktop audio input is muted or unmuted. + * @function Audio.desktopMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. + * @returns {Signal} + */ + void desktopMutedChanged(bool isMuted); + + /**jsdoc + * Triggered when HMD audio input is muted or unmuted. + * @function Audio.hmdMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. + * @returns {Signal} + */ + void hmdMutedChanged(bool isMuted); + + /** + * Triggered when Push-to-Talk has been enabled or disabled. + * @function Audio.pushToTalkChanged + * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. + * @returns {Signal} + */ + void pushToTalkChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. + * @function Audio.pushToTalkDesktopChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkDesktopChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. + * @function Audio.pushToTalkHMDChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkHMDChanged(bool enabled); + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -237,6 +306,14 @@ signals: */ void contextChanged(const QString& context); + /**jsdoc + * Triggered when pushing to talk. + * @function Audio.pushingToTalkChanged + * @param {boolean} talking - true if broadcasting with PTT, false otherwise. + * @returns {Signal} + */ + void pushingToTalkChanged(bool talking); + public slots: /**jsdoc @@ -245,6 +322,8 @@ public slots: */ void onContextChanged(); + void handlePushedToTalk(bool enabled); + private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); @@ -260,11 +339,19 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; bool _isClipping { false }; - bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; + Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; + Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; + Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; + bool _desktopMuted{ true }; + bool _hmdMuted{ false }; + bool _pttDesktop{ false }; + bool _pttHMD{ false }; + bool _pushingToTalk{ false }; }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index ee82c0c6ea..51d070d8cd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -28,6 +28,9 @@ var UNMUTE_ICONS = { }; function onMuteToggled() { + if (Audio.pushingToTalk) { + return; + } if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { From eeb900b76165b0277ef17435d5e8c774ce85d9b5 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:00:09 -0800 Subject: [PATCH 051/132] laying groundwork for audio app + fixing deadlocks --- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- interface/src/scripting/Audio.cpp | 108 +++++++++--------- interface/src/scripting/Audio.h | 1 + scripts/system/audio.js | 11 +- 4 files changed, 69 insertions(+), 57 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f91058bc3c..2ab1085408 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,7 +134,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index fe04ce47ca..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -66,32 +66,30 @@ bool Audio::isMuted() const { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getMutedHMD(); - } - else { + } else { return getMutedDesktop(); } } void Audio::setMuted(bool isMuted) { - withWriteLock([&] { - bool isHMD = qApp->isHMDMode(); - if (isHMD) { - setMutedHMD(isMuted); - } - else { - setMutedDesktop(isMuted); - } - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } else { + setMutedDesktop(isMuted); + } } void Audio::setMutedDesktop(bool isMuted) { bool changed = false; - if (_desktopMuted != isMuted) { - changed = true; - _desktopMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit desktopMutedChanged(isMuted); @@ -106,12 +104,14 @@ bool Audio::getMutedDesktop() const { void Audio::setMutedHMD(bool isMuted) { bool changed = false; - if (_hmdMuted != isMuted) { - changed = true; - _hmdMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit hmdMutedChanged(isMuted); @@ -128,12 +128,24 @@ bool Audio::getPTT() { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getPTTHMD(); - } - else { + } else { return getPTTDesktop(); } } +void scripting::Audio::setPushingToTalk(bool pushingToTalk) { + bool changed = false; + withWriteLock([&] { + if (_pushingToTalk != pushingToTalk) { + changed = true; + _pushingToTalk = pushingToTalk; + } + }); + if (changed) { + emit pushingToTalkChanged(pushingToTalk); + } +} + bool Audio::getPushingToTalk() const { return resultWithReadLock([&] { return _pushingToTalk; @@ -144,8 +156,7 @@ void Audio::setPTT(bool enabled) { bool isHMD = qApp->isHMDMode(); if (isHMD) { setPTTHMD(enabled); - } - else { + } else { setPTTDesktop(enabled); } } @@ -156,16 +167,16 @@ void Audio::setPTTDesktop(bool enabled) { if (_pttDesktop != enabled) { changed = true; _pttDesktop = enabled; - if (!enabled) { - // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. - setMutedDesktop(true); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedDesktop(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkDesktopChanged(enabled); @@ -184,16 +195,16 @@ void Audio::setPTTHMD(bool enabled) { if (_pttHMD != enabled) { changed = true; _pttHMD = enabled; - if (!enabled) { - // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. - setMutedHMD(false); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedHMD(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkHMDChanged(enabled); @@ -318,8 +329,7 @@ void Audio::onContextChanged() { }); if (isHMD) { setMuted(getMutedHMD()); - } - else { + } else { setMuted(getMutedDesktop()); } if (changed) { @@ -331,14 +341,10 @@ void Audio::handlePushedToTalk(bool enabled) { if (getPTT()) { if (enabled) { setMuted(false); - } - else { + } else { setMuted(true); } - if (_pushingToTalk != enabled) { - _pushingToTalk = enabled; - emit pushingToTalkChanged(enabled); - } + setPushingToTalk(enabled); } } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 6aa589e399..94f8a7bf54 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -99,6 +99,7 @@ public: bool getMutedHMD() const; void setPTT(bool enabled); bool getPTT(); + void setPushingToTalk(bool pushingToTalk); bool getPushingToTalk() const; // Push-To-Talk setters and getters diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 51d070d8cd..bf44cfa7cc 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -26,12 +26,15 @@ var UNMUTE_ICONS = { icon: "icons/tablet-icons/mic-unmute-i.svg", activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; function onMuteToggled() { if (Audio.pushingToTalk) { - return; - } - if (Audio.muted) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { button.editProperties(UNMUTE_ICONS); @@ -71,6 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); +Audio.pushingToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -79,6 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushingToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 066d1797cf6f96c722274b18b33d4010ed60405a Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:11:47 -0800 Subject: [PATCH 052/132] Push to talk changes + rebased with master (#7) Push to talk changes + rebased with master --- interface/src/scripting/Audio.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 63ce9d2b2e..45bb15f1a3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,6 +231,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; From 9613cfb6b9da73248c95151ddc15d002b85a87b2 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:18:57 -0800 Subject: [PATCH 053/132] changing text display --- interface/resources/qml/hifi/audio/MicBar.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 2ab1085408..9d1cbfbc6c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; + visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -155,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } From 0cb9e7eb3609189d305df8cb700c5225e801b951 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:45:43 -0800 Subject: [PATCH 054/132] exposing setting pushingToTalk --- interface/src/scripting/Audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 94f8a7bf54..9ad4aac9c1 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -72,7 +72,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) - Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk WRITE setPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; From 7b197c975c9b5926e48cb2d2847e490d8f9ad0d8 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 17:29:32 -0800 Subject: [PATCH 055/132] updating compile failure + icons/settings update --- .../icons/tablet-icons/mic-ptt-a.svg | 1 + .../icons/tablet-icons/mic-ptt-i.svg | 24 +++++++ interface/resources/qml/hifi/audio/Audio.qml | 64 ++++++++++++++++--- interface/resources/qml/hifi/audio/MicBar.qml | 8 ++- interface/src/scripting/Audio.cpp | 20 ------ scripts/system/audio.js | 14 ++-- 6 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-a.svg create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-i.svg diff --git a/interface/resources/icons/tablet-icons/mic-ptt-a.svg b/interface/resources/icons/tablet-icons/mic-ptt-a.svg new file mode 100644 index 0000000000..e6df3c69d7 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-a.svg @@ -0,0 +1 @@ +mic-ptt-a \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/mic-ptt-i.svg b/interface/resources/icons/tablet-icons/mic-ptt-i.svg new file mode 100644 index 0000000000..2141ea5229 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-i.svg @@ -0,0 +1,24 @@ + + + + + + diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 45358f59a2..d44a9c862e 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -120,6 +120,10 @@ Rectangle { isRedCheck: true; checked: AudioScriptingInterface.muted; onClicked: { + if (AudioScriptingInterface.pushToTalk && !checked) { + // disable push to talk if unmuting + AudioScriptingInterface.pushToTalk = false; + } AudioScriptingInterface.muted = checked; checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } @@ -150,7 +154,23 @@ Rectangle { } AudioControls.CheckBox { spacing: muteMic.spacing - text: qsTr("Push To Talk"); + text: qsTr("Show audio level meter"); + checked: AvatarInputs.showAudioTools; + onClicked: { + AvatarInputs.showAudioTools = checked; + checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + } + onXChanged: rightMostInputLevelPos = x + width + } + } + + Separator {} + + ColumnLayout { + spacing: muteMic.spacing; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { if (isVR) { @@ -167,15 +187,41 @@ Rectangle { }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); - checked: AvatarInputs.showAudioTools; - onClicked: { - AvatarInputs.showAudioTools = checked; - checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + Item { + id: pttTextContainer + x: margins.paddings; + width: rightMostInputLevelPos + height: pttTextMetrics.height + visible: true + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + wrapMode: Text.WordWrap + color: hifi.colors.white; + width: parent.width; + font.italic: true + size: 16; + text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : + qsTr("Press and hold the button \"T\" to unmute."); + onTextChanged: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } + } + } + Component.onCompleted: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } } - onXChanged: rightMostInputLevelPos = x + width } } } diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 9d1cbfbc6c..50477b82f8 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -67,6 +67,9 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { + if (AudioScriptingInterface.pushToTalk) { + return; + } AudioScriptingInterface.muted = !AudioScriptingInterface.muted; Tablet.playSound(TabletEnums.ButtonClick); } @@ -109,9 +112,10 @@ Rectangle { Image { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; id: image; - source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; width: 30; height: 30; @@ -155,7 +159,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED PTT-(T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 45bb15f1a3..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,26 +231,6 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index bf44cfa7cc..19ed3faef2 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -27,12 +27,12 @@ var UNMUTE_ICONS = { activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; var PTT_ICONS = { - icon: "icons/tablet-icons/mic-unmute-i.svg", - activeIcon: "icons/tablet-icons/mic-unmute-a.svg" + icon: "icons/tablet-icons/mic-ptt-i.svg", + activeIcon: "icons/tablet-icons/mic-ptt-a.svg" }; function onMuteToggled() { - if (Audio.pushingToTalk) { + if (Audio.pushToTalk) { button.editProperties(PTT_ICONS); } else if (Audio.muted) { button.editProperties(MUTE_ICONS); @@ -63,8 +63,8 @@ function onScreenChanged(type, url) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ - icon: Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, - activeIcon: Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, + icon: Audio.pushToTalk ? PTT_ICONS.icon : Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, + activeIcon: Audio.pushToTalk ? PTT_ICONS.activeIcon : Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, text: TABLET_BUTTON_NAME, sortOrder: 1 }); @@ -74,7 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); -Audio.pushingToTalkChanged.connect(onMuteToggled); +Audio.pushToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -83,7 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); - Audio.pushingToTalkChanged.disconnect(onMuteToggled); + Audio.pushToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 2536fcaa1794c30813544e616f1ca4666c7a05a3 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:28 -0800 Subject: [PATCH 056/132] Add pushToTalk.js controllerModule. --- .../controllerModules/pushToTalk.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/system/controllers/controllerModules/pushToTalk.js diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js new file mode 100644 index 0000000000..e764b228c9 --- /dev/null +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -0,0 +1,75 @@ +"use strict"; + +// Created by Jason C. Najera on 3/7/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Handles Push-to-Talk functionality for HMD mode. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { // BEGIN LOCAL_SCOPE + function PushToTalkHandler() { + var _this = this; + this.active = false; + //var pttMapping, mappingName; + + this.setup = function() { + //mappingName = 'Hifi-PTT-Dev-' + Math.random(); + //pttMapping = Controller.newMapping(mappingName); + //pttMapping.enable(); + }; + + this.shouldTalk = function (controllerData) { + // Set up test against controllerData here... + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? true : false; + }; + + this.shouldStopTalking = function (controllerData) { + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? false : true; + }; + + this.isReady = function (controllerData, deltaTime) { + if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + Audio.pushingToTalk = true; + returnMakeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { + Audio.pushingToTalk = false; + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + //pttMapping.disable(); + }; + + this.parameters = makeDispatcherModuleParameters( + 950, + ["head"], + [], + 100); + } + + var pushToTalk = new PushToTalkHandler(); + enableDispatcherModule("PushToTalk", pushToTalk); + + function cleanup () { + pushToTalk.cleanup(); + disableDispatcherModule("PushToTalk"); + }; + + Script.scriptEnding.connect(cleanup); +}()); // END LOCAL_SCOPE \ No newline at end of file From 9d59e68d45d53e53d6471993dd34bca6695ddfab Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:48 -0800 Subject: [PATCH 057/132] Enable pushToTalk.js controller module. --- scripts/system/controllers/controllerDispatcher.js | 1 + scripts/system/controllers/controllerScripts.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 28c3e2a299..f4c0cbb0d6 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -58,6 +58,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. this.activitySlots = { + head: false, leftHand: false, rightHand: false, rightHandTrigger: false, diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 86ff7701c3..d236d6b12a 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -34,7 +34,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearGrabHyperLinkEntity.js", "controllerModules/nearTabletHighlight.js", "controllerModules/nearGrabEntity.js", - "controllerModules/farGrabEntity.js" + "controllerModules/farGrabEntity.js", + "controllerModules/pushToTalk.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From 55efc315f10604c663b8df2fa6f65c7dce7d4f3f Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:45:59 -0800 Subject: [PATCH 058/132] Fix activation / deactivation criteria for PTT controller module. --- scripts/system/controllers/controllerModules/pushToTalk.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index e764b228c9..557476ccd7 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -25,12 +25,12 @@ Script.include("/~/system/libraries/controllers.js"); this.shouldTalk = function (controllerData) { // Set up test against controllerData here... - var gripVal = controllerData.secondaryValues[this.hand]; + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; return (gripVal) ? true : false; }; this.shouldStopTalking = function (controllerData) { - var gripVal = controllerData.secondaryValues[this.hand]; + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; return (gripVal) ? false : true; }; From a62cadc54145dcbc54bf17d6f13d1277a1832247 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 12:35:04 -0800 Subject: [PATCH 059/132] Fix typo. --- scripts/system/controllers/controllerModules/pushToTalk.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index 557476ccd7..dd959ae6fb 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -35,7 +35,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData, deltaTime) { - if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { Audio.pushingToTalk = true; returnMakeRunningValues(true, [], []); } From b83e9f70e64ff7717f1fda7a5ed7d7401cdf304b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 7 Mar 2019 12:36:56 -0800 Subject: [PATCH 060/132] adding PushToTalk action --- interface/src/Application.cpp | 11 +++++++++++ libraries/controllers/src/controllers/Actions.cpp | 4 ++++ libraries/controllers/src/controllers/Actions.h | 1 + .../controllers/controllerModules/pushToTalk.js | 4 ++-- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fa63757560..1582c69bc9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1601,12 +1601,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { using namespace controller; auto tabletScriptingInterface = DependencyManager::get(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); { auto actionEnum = static_cast(action); int key = Qt::Key_unknown; static int lastKey = Qt::Key_unknown; bool navAxis = false; switch (actionEnum) { + case Action::TOGGLE_PUSHTOTALK: + if (audioScriptingInterface->getPTT()) { + qDebug() << "State is " << state; + if (state > 0.0f) { + audioScriptingInterface->setPushingToTalk(false); + } else if (state < 0.0f) { + audioScriptingInterface->setPushingToTalk(true); + } + } + case Action::UI_NAV_VERTICAL: navAxis = true; if (state > 0.0f) { diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 5a396231b6..57be2f788b 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -180,6 +180,7 @@ namespace controller { * third person, to full screen mirror, then back to first person and repeat. * ContextMenunumbernumberShow / hide the tablet. * ToggleMutenumbernumberToggle the microphone mute. + * TogglePushToTalknumbernumberToggle push to talk. * ToggleOverlaynumbernumberToggle the display of overlays. * SprintnumbernumberSet avatar sprint mode. * ReticleClicknumbernumberSet mouse-pressed. @@ -245,6 +246,8 @@ namespace controller { * ContextMenu instead. * TOGGLE_MUTEnumbernumberDeprecated: Use * ToggleMute instead. + * TOGGLE_PUSHTOTALKnumbernumberDeprecated: Use + * TogglePushToTalk instead. * SPRINTnumbernumberDeprecated: Use * Sprint instead. * LONGITUDINAL_BACKWARDnumbernumberDeprecated: Use @@ -411,6 +414,7 @@ namespace controller { makeButtonPair(Action::ACTION2, "SecondaryAction"), makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"), makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"), + makeButtonPair(Action::TOGGLE_PUSHTOTALK, "TogglePushToTalk"), makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"), makeButtonPair(Action::TOGGLE_OVERLAY, "ToggleOverlay"), makeButtonPair(Action::SPRINT, "Sprint"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index a12a3d60a9..3e99d8d147 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -60,6 +60,7 @@ enum class Action { CONTEXT_MENU, TOGGLE_MUTE, + TOGGLE_PUSHTOTALK, CYCLE_CAMERA, TOGGLE_OVERLAY, diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index e764b228c9..916769934d 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -35,7 +35,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData, deltaTime) { - if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { Audio.pushingToTalk = true; returnMakeRunningValues(true, [], []); } @@ -72,4 +72,4 @@ Script.include("/~/system/libraries/controllers.js"); }; Script.scriptEnding.connect(cleanup); -}()); // END LOCAL_SCOPE \ No newline at end of file +}()); // END LOCAL_SCOPE From 472c7ffab443afbeaa31b090a07f1ddfd475e5ad Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 7 Mar 2019 12:54:32 -0800 Subject: [PATCH 061/132] removing debug statement --- interface/src/Application.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1582c69bc9..16f4a9094f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1610,7 +1610,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo switch (actionEnum) { case Action::TOGGLE_PUSHTOTALK: if (audioScriptingInterface->getPTT()) { - qDebug() << "State is " << state; if (state > 0.0f) { audioScriptingInterface->setPushingToTalk(false); } else if (state < 0.0f) { From af03fb5f85dd376326d4a1d8e62ad822546b758e Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 7 Mar 2019 16:23:12 -0800 Subject: [PATCH 062/132] try to fix text rendering on amd with transparency --- interface/src/Application.cpp | 9 ++--- .../src/RenderablePolyLineEntityItem.cpp | 9 ++--- .../src/RenderableShapeEntityItem.cpp | 9 ++--- .../src/RenderableTextEntityItem.cpp | 8 +++-- libraries/render-utils/src/GeometryCache.cpp | 11 ++---- libraries/render-utils/src/TextRenderer3D.cpp | 4 +-- libraries/render-utils/src/TextRenderer3D.h | 2 +- libraries/render-utils/src/sdf_text3D.slv | 3 +- .../src/sdf_text3D_transparent.slf | 34 ++++++++++++++++--- libraries/render-utils/src/text/Font.cpp | 4 +-- libraries/render-utils/src/text/Font.h | 2 +- libraries/shared/src/DisableDeferred.h | 24 +++++++++++++ 12 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 libraries/shared/src/DisableDeferred.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8ab4d10db..b854387fd8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -246,6 +246,8 @@ #include "AboutUtil.h" +#include + #if defined(Q_OS_WIN) #include @@ -289,13 +291,6 @@ static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" }; static bool DISABLE_WATCHDOG = nsightActive() || QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG); #endif -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - #if !defined(Q_OS_ANDROID) static const uint32_t MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; #else diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 454e8b136a..7050393221 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include "paintStroke_Shared.slh" using namespace render; @@ -29,13 +31,6 @@ gpu::PipelinePointer PolyLineEntityRenderer::_glowPipeline = nullptr; static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png"); -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { _texture = DependencyManager::get()->getTexture(DEFAULT_POLYLINE_TEXTURE); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index b61bb2cbda..20837070d8 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -19,6 +19,8 @@ #include "RenderPipelines.h" +#include + //#define SHAPE_ENTITY_USE_FADE_EFFECT #ifdef SHAPE_ENTITY_USE_FADE_EFFECT #include @@ -30,13 +32,6 @@ 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; -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - 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"); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index dfc9277bf0..107847826c 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -19,6 +19,8 @@ #include "GLMHelpers.h" +#include + using namespace render; using namespace render::entities; @@ -160,6 +162,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { glm::vec4 backgroundColor; Transform modelTransform; glm::vec3 dimensions; + bool forwardRendered; withReadLock([&] { modelTransform = _renderTransform; dimensions = _dimensions; @@ -169,6 +172,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { textColor = EntityRenderer::calculatePulseColor(textColor, _pulseProperties, _created); backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha); backgroundColor = EntityRenderer::calculatePulseColor(backgroundColor, _pulseProperties, _created); + forwardRendered = _renderLayer != RenderLayer::WORLD || DISABLE_DEFERRED; }); // Render background @@ -188,7 +192,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { if (backgroundColor.a > 0.0f) { batch.setModelTransform(transformToTopLeft); auto geometryCache = DependencyManager::get(); - geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false); + geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false, true, forwardRendered); geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); } @@ -199,7 +203,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { batch.setModelTransform(transformToTopLeft); glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), dimensions.y - (_topMargin + _bottomMargin)); - _textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale); + _textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale, forwardRendered); } } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 7d29b9e792..e322dc9d2b 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -35,10 +35,10 @@ #include "StencilMaskPass.h" #include "FadeEffect.h" - - #include "DeferredLightingEffect.h" +#include + namespace gr { using graphics::slot::texture::Texture; using graphics::slot::buffer::Buffer; @@ -49,13 +49,6 @@ namespace ru { using render_utils::slot::buffer::Buffer; } -#if defined(USE_GLES) -static bool DISABLE_DEFERRED = true; -#else -static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; -static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); -#endif - //#define WANT_DEBUG // @note: Originally size entity::NUM_SHAPES diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 2d507ba21b..93edc4217d 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -67,11 +67,11 @@ float TextRenderer3D::getFontSize() const { } void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - const glm::vec2& bounds) { + const glm::vec2& bounds, bool forwardRendered) { // The font does all the OpenGL work if (_font) { _color = color; - _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds); + _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, forwardRendered); } } diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 97b74fcabd..b6475ab0ed 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -39,7 +39,7 @@ public: float getFontSize() const; // Pixel size void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); + const glm::vec2& bounds = glm::vec2(-1.0f), bool forwardRendered = false); private: TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false, diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv index 04ee44510a..5f4df86d56 100644 --- a/libraries/render-utils/src/sdf_text3D.slv +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -19,6 +19,7 @@ // the interpolated normal layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; +layout(location=RENDER_UTILS_ATTR_POSITION_ES) out vec4 _positionES; void main() { _texCoord01.xy = inTexCoord0.xy; @@ -26,7 +27,7 @@ void main() { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> const vec3 normal = vec3(0, 0, 1); <$transformModelToWorldDir(cam, obj, normal, _normalWS)$> } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text3D_transparent.slf b/libraries/render-utils/src/sdf_text3D_transparent.slf index 2fbfb2ca43..311c849915 100644 --- a/libraries/render-utils/src/sdf_text3D_transparent.slf +++ b/libraries/render-utils/src/sdf_text3D_transparent.slf @@ -10,7 +10,14 @@ // 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 DefaultMaterials.slh@> + +<@include ForwardGlobalLight.slh@> +<$declareEvalGlobalLightingAlphaBlended()$> + +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include render-utils/ShaderConstants.h@> LAYOUT(binding=0) uniform sampler2D Font; @@ -24,12 +31,14 @@ LAYOUT(binding=0) uniform textParamsBuffer { TextParams params; }; -// the interpolated normal +layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; #define _texCoord0 _texCoord01.xy #define _texCoord1 _texCoord01.zw +layout(location=0) out vec4 _fragColor0; + #define TAA_TEXTURE_LOD_BIAS -3.0 const float interiorCutoff = 0.8; @@ -57,9 +66,24 @@ void main() { a += evalSDF(_texCoord0 + dxTexCoord + dyTexCoord); a *= 0.25; - packDeferredFragmentTranslucent( + float alpha = a * params.color.a; + if (alpha <= 0.0) { + discard; + } + + TransformCamera cam = getTransformCamera(); + vec3 fragPosition = _positionES.xyz; + + _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( + cam._viewInverse, + 1.0, + DEFAULT_OCCLUSION, + fragPosition, normalize(_normalWS), - a * params.color.a, params.color.rgb, - DEFAULT_ROUGHNESS); + DEFAULT_FRESNEL, + DEFAULT_METALLIC, + DEFAULT_EMISSIVE, + DEFAULT_ROUGHNESS, alpha), + alpha); } \ No newline at end of file diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index cc451eeedc..e0e99da020 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -343,7 +343,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm } void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, - EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds) { + EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool forwardRendered) { if (str == "") { return; } @@ -370,7 +370,7 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString } // need the gamma corrected color here - batch.setPipeline((color.a < 1.0f) ? _transparentPipeline : _pipeline); + batch.setPipeline(forwardRendered || (color.a < 1.0f) ? _transparentPipeline : _pipeline); batch.setInputFormat(_format); batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride); batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 85f692f5d8..26cc4e46c3 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -46,7 +46,7 @@ public: // Render string to batch void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, const glm::vec4& color, EffectType effectType, - const glm::vec2& origin, const glm::vec2& bound); + const glm::vec2& origin, const glm::vec2& bound, bool forwardRendered); static Pointer load(const QString& family); diff --git a/libraries/shared/src/DisableDeferred.h b/libraries/shared/src/DisableDeferred.h new file mode 100644 index 0000000000..9a1f9be117 --- /dev/null +++ b/libraries/shared/src/DisableDeferred.h @@ -0,0 +1,24 @@ +// +// Created by Sam Gondelman on 3/7/19. +// Copyright 2019 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_DisableDeferred_h +#define hifi_DisableDeferred_h + +#include +#include + +#if defined(USE_GLES) +static bool DISABLE_DEFERRED = true; +#else +static const QString RENDER_FORWARD{ "HIFI_RENDER_FORWARD" }; +static bool DISABLE_DEFERRED = QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); +#endif + + +#endif // hifi_DisableDeferred_h + From 236c61dae53dedb39a7be9dd87d672c88b788bfd Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 7 Mar 2019 17:39:41 -0800 Subject: [PATCH 063/132] Add ModelCache singleton & other requirements for resource handling Cherry-pick of 6b5598bdc5e562119c55 from 79-HERO. --- assignment-client/src/avatars/AvatarMixer.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index e4077d5d46..33e1034128 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -35,6 +35,8 @@ #include #include "../AssignmentDynamicFactory.h" #include "../entities/AssignmentParentFinder.h" +#include +#include const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; @@ -60,7 +62,10 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : { DependencyManager::registerInheritance(); DependencyManager::set(); - + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); // make sure we hear about node kills so we can tell the other nodes connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); @@ -1060,6 +1065,10 @@ void AvatarMixer::handleOctreePacket(QSharedPointer message, Sh } void AvatarMixer::aboutToFinish() { + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); From bcd00f98d08253e03fce26baed669b11bcb41afa Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 26 Feb 2019 21:18:32 +0100 Subject: [PATCH 064/132] - added more warnings to the avatar doctor - read embedded fst texture mappings --- interface/src/avatar/AvatarDoctor.cpp | 448 ++++++++++++++++++++------ interface/src/avatar/AvatarDoctor.h | 27 +- 2 files changed, 371 insertions(+), 104 deletions(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index b528441be7..66433f005b 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -14,8 +14,61 @@ #include #include +#include -AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) : +const int NETWORKED_JOINTS_LIMIT = 256; +const float HIPS_GROUND_MIN_Y = 0.01f; +const float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; +const QString LEFT_JOINT_PREFIX = "Left"; +const QString RIGHT_JOINT_PREFIX = "Right"; + +const QStringList LEG_MAPPING_SUFFIXES = { + "UpLeg" + "Leg", + "Foot", + "ToeBase", +}; + +static QStringList ARM_MAPPING_SUFFIXES = { + "Shoulder", + "Arm", + "ForeArm", + "Hand", +}; + +static QStringList HAND_MAPPING_SUFFIXES = { + "HandIndex3", + "HandIndex2", + "HandIndex1", + "HandPinky3", + "HandPinky2", + "HandPinky1", + "HandMiddle3", + "HandMiddle2", + "HandMiddle1", + "HandRing3", + "HandRing2", + "HandRing1", + "HandThumb3", + "HandThumb2", + "HandThumb1", +}; + +const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); + +DiagnosableAvatar::DiagnosableAvatar(QThread* thread) : Avatar(thread) { + // give the pointer to our head to inherited _headData variable from AvatarData + _headData = new Head(this); + _skeletonModel = std::make_shared(this, nullptr); + _skeletonModel->setLoadingPriority(MYAVATAR_LOADING_PRIORITY); + connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); + connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); + connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); +} + +DiagnosableAvatar::~DiagnosableAvatar() = default; + +AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) : _avatarFSTFileUrl(avatarFSTFileUrl) { connect(this, &AvatarDoctor::complete, this, [this](QVariantList errors) { @@ -39,136 +92,206 @@ void AvatarDoctor::startDiagnosing() { const auto resource = DependencyManager::get()->getGeometryResource(_avatarFSTFileUrl); resource->refresh(); - const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); - const auto resourceLoaded = [this, resource, DEFAULT_URL](bool success) { + + const auto resourceLoaded = [this, resource](bool success) { // MODEL if (!success) { - _errors.push_back({ "Model file cannot be opened", DEFAULT_URL }); + _errors.push_back({ "Model file cannot be opened.", DEFAULT_URL }); emit complete(getErrors()); return; } + _model = resource; const auto model = resource.data(); const auto avatarModel = resource.data()->getHFMModel(); if (!avatarModel.originalURL.endsWith(".fbx")) { - _errors.push_back({ "Unsupported avatar model format", DEFAULT_URL }); + _errors.push_back({ "Unsupported avatar model format.", DEFAULT_URL }); emit complete(getErrors()); return; } // RIG if (avatarModel.joints.isEmpty()) { - _errors.push_back({ "Avatar has no rig", DEFAULT_URL }); + _errors.push_back({ "Avatar has no rig.", DEFAULT_URL }); } else { - if (avatarModel.joints.length() > 256) { - _errors.push_back({ "Avatar has over 256 bones", DEFAULT_URL }); + auto jointNames = avatarModel.getJointNames(); + + if (avatarModel.joints.length() > NETWORKED_JOINTS_LIMIT) { + _errors.push_back({tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), DEFAULT_URL }); } // Avatar does not have Hips bone mapped - if (!avatarModel.getJointNames().contains("Hips")) { - _errors.push_back({ "Hips are not mapped", DEFAULT_URL }); + if (!jointNames.contains("Hips")) { + _errors.push_back({ "Hips are not mapped.", DEFAULT_URL }); } - if (!avatarModel.getJointNames().contains("Spine")) { - _errors.push_back({ "Spine is not mapped", DEFAULT_URL }); + if (!jointNames.contains("Spine")) { + _errors.push_back({ "Spine is not mapped.", DEFAULT_URL }); } - if (!avatarModel.getJointNames().contains("Head")) { - _errors.push_back({ "Head is not mapped", DEFAULT_URL }); + if (!jointNames.contains("Spine1")) { + _errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_URL }); + } + if (!jointNames.contains("Neck")) { + _errors.push_back({ "Neck is not mapped.", DEFAULT_URL }); + } + if (!jointNames.contains("Head")) { + _errors.push_back({ "Head is not mapped.", DEFAULT_URL }); + } + + if (!jointNames.contains("LeftEye")) { + if (jointNames.contains("RightEye")) { + _errors.push_back({ "LeftEye is not mapped.", DEFAULT_URL }); + } else { + _errors.push_back({ "Eyes are not mapped.", DEFAULT_URL }); + } + } else if (!jointNames.contains("RightEye")) { + _errors.push_back({ "RightEye is not mapped.", DEFAULT_URL }); + } + + const auto checkJointAsymmetry = [jointNames] (const QStringList& jointMappingSuffixes) { + foreach (const QString& jointSuffix, jointMappingSuffixes) { + if (jointNames.contains(LEFT_JOINT_PREFIX + jointSuffix) != + jointNames.contains(RIGHT_JOINT_PREFIX + jointSuffix)) { + return true; + } + } + return false; + }; + + const auto isDescendantOfJointWhenJointsExist = [avatarModel, jointNames] (const QString& jointName, const QString& ancestorName) { + if (!jointNames.contains(jointName) || !jointNames.contains(ancestorName)) { + return true; + } + auto currentJoint = avatarModel.joints[avatarModel.jointIndices[jointName] - 1]; + while (currentJoint.parentIndex != -1) { + currentJoint = avatarModel.joints[currentJoint.parentIndex]; + if (currentJoint.name == ancestorName) { + return true; + } + } + return false; + }; + + if (checkJointAsymmetry(ARM_MAPPING_SUFFIXES)) { + _errors.push_back({ "Asymmetrical arm bones.", DEFAULT_URL }); + } + if (checkJointAsymmetry(HAND_MAPPING_SUFFIXES)) { + _errors.push_back({ "Asymmetrical hand bones.", DEFAULT_URL }); + } + if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) { + _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); + } + _avatar = new DiagnosableAvatar(QThread::currentThread()); + + _avatar->setSkeletonModelURL(_avatarFSTFileUrl); + if (_avatar->getSkeletonModel()->updateGeometry()) { + // Rig has been fully loaded + + // SCALE + const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; + const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; + + const float avatarHeight = _avatar->getHeight(); + if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { + _errors.push_back({ "Avatar is possibly too short.", DEFAULT_URL }); + } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { + _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_URL }); + } + + auto rig = &_avatar->getSkeletonModel()->getRig(); + + // HipsNotOnGround + auto hipsIndex = rig->indexOfJoint("Hips"); + if (hipsIndex >= 0) { + glm::vec3 hipsPosition; + if (rig->getJointPosition(hipsIndex, hipsPosition)) { + const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips")); + + if (hipsPosition.y < HIPS_GROUND_MIN_Y) { + _errors.push_back({ "Hips are on ground.", DEFAULT_URL }); + } + } + } + + // HipsSpineChestNotCoincident + auto spineIndex = rig->indexOfJoint("Spine"); + auto chestIndex = rig->indexOfJoint("Spine1"); + if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) { + glm::vec3 hipsPosition; + glm::vec3 spinePosition; + glm::vec3 chestPosition; + if (rig->getJointPosition(hipsIndex, hipsPosition) && + rig->getJointPosition(spineIndex, spinePosition) && + rig->getJointPosition(chestIndex, chestPosition)) { + + const auto hipsToSpine = glm::length(hipsPosition - spinePosition); + const auto spineToChest = glm::length(spinePosition - chestPosition); + if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) { + _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_URL }); + } + } + } + } + _avatar->deleteLater(); + _avatar = nullptr; + + auto mapping = resource->getMapping(); + + if (mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { + const auto& jointNameMappings = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); + QStringList jointValues; + foreach(const auto& jointVariant, jointNameMappings.values()) { + jointValues << jointVariant.toString(); + } + + const auto& uniqueJointValues = jointValues.toSet(); + foreach (const auto& jointName, uniqueJointValues) { + if (jointValues.count(jointName) > 1) { + _errors.push_back({ tr("%1 is mapped multiple times.").arg(jointName), DEFAULT_URL }); + } + } + } + + if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) { + _errors.push_back({ "Spine is no child of Hips.", DEFAULT_URL }); + } + + if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) { + _errors.push_back({ "Spine1 is no child of Spine.", DEFAULT_URL }); + } + + if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) { + _errors.push_back({ "Head is no child of Spine1.", DEFAULT_URL }); } } - // SCALE - const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; - const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; - - const float avatarHeight = avatarModel.bindExtents.largestDimension(); - if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { - _errors.push_back({ "Avatar is possibly too small.", DEFAULT_URL }); - } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { - _errors.push_back({ "Avatar is possibly too large.", DEFAULT_URL }); - } - - // TEXTURES - QStringList externalTextures{}; - QSet textureNames{}; - auto addTextureToList = [&externalTextures](hfm::Texture texture) mutable { - if (!texture.filename.isEmpty() && texture.content.isEmpty() && !externalTextures.contains(texture.name)) { - externalTextures << texture.name; + auto materialMappingHandled = [this]() mutable { + _materialMappingLoadedCount++; + // Continue diagnosing the textures as soon as the material mappings have tried to load. + if (_materialMappingLoadedCount == _materialMappingCount) { + // TEXTURES + diagnoseTextures(); } }; - - foreach(const HFMMaterial material, avatarModel.materials) { - addTextureToList(material.normalTexture); - addTextureToList(material.albedoTexture); - addTextureToList(material.opacityTexture); - addTextureToList(material.glossTexture); - addTextureToList(material.roughnessTexture); - addTextureToList(material.specularTexture); - addTextureToList(material.metallicTexture); - addTextureToList(material.emissiveTexture); - addTextureToList(material.occlusionTexture); - addTextureToList(material.scatteringTexture); - addTextureToList(material.lightmapTexture); - } - if (!externalTextures.empty()) { - // Check External Textures: - auto modelTexturesURLs = model->getTextures(); - _externalTextureCount = externalTextures.length(); - foreach(const QString textureKey, externalTextures) { - if (!modelTexturesURLs.contains(textureKey)) { - _missingTextureCount++; - _checkedTextureCount++; - continue; - } - - const QUrl textureURL = modelTexturesURLs[textureKey].toUrl(); - - auto textureResource = DependencyManager::get()->getTexture(textureURL); - auto checkTextureLoadingComplete = [this, DEFAULT_URL] () mutable { - qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount; - - if (_checkedTextureCount == _externalTextureCount) { - if (_missingTextureCount > 0) { - _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL }); - } - if (_unsupportedTextureCount > 0) { - _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), DEFAULT_URL }); - } - emit complete(getErrors()); - } - }; - - auto textureLoaded = [this, textureResource, checkTextureLoadingComplete] (bool success) mutable { - if (!success) { - auto normalizedURL = DependencyManager::get()->normalizeURL(textureResource->getURL()); - if (normalizedURL.isLocalFile()) { - QFile textureFile(normalizedURL.toLocalFile()); - if (textureFile.exists()) { - _unsupportedTextureCount++; - } else { - _missingTextureCount++; - } - } else { - _missingTextureCount++; - } - } - _checkedTextureCount++; - checkTextureLoadingComplete(); - }; - - if (textureResource) { - textureResource->refresh(); - if (textureResource->isLoaded()) { - textureLoaded(!textureResource->isFailed()); - } else { - connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded); - } + _materialMappingCount = (int)model->getMaterialMapping().size(); + _materialMappingLoadedCount = 0; + foreach(const auto& materialMapping, model->getMaterialMapping()) { + // refresh the texture mappings + auto materialMappingResource = materialMapping.second; + if (materialMappingResource) { + materialMappingResource->refresh(); + if (materialMappingResource->isLoaded()) { + materialMappingHandled(); } else { - _missingTextureCount++; - _checkedTextureCount++; - checkTextureLoadingComplete(); + connect(materialMappingResource.data(), &NetworkTexture::finished, this, [materialMappingHandled](bool success) mutable { + materialMappingHandled(); + }); } + } else { + materialMappingHandled(); } - } else { - emit complete(getErrors()); + } + if (_materialMappingCount == 0) { + // TEXTURES + diagnoseTextures(); } }; @@ -184,6 +307,125 @@ void AvatarDoctor::startDiagnosing() { } } +void AvatarDoctor::diagnoseTextures() { + const auto model = _model.data(); + const auto avatarModel = _model.data()->getHFMModel(); + QStringList externalTextures{}; + QSet textureNames{}; + int texturesFound = 0; + auto addTextureToList = [&externalTextures, &texturesFound](hfm::Texture texture) mutable { + if (texture.filename.isEmpty()) { + return; + } + if (texture.content.isEmpty() && !externalTextures.contains(texture.name)) { + externalTextures << texture.name; + } + texturesFound++; + }; + + foreach(const HFMMaterial material, avatarModel.materials) { + addTextureToList(material.normalTexture); + addTextureToList(material.albedoTexture); + addTextureToList(material.opacityTexture); + addTextureToList(material.glossTexture); + addTextureToList(material.roughnessTexture); + addTextureToList(material.specularTexture); + addTextureToList(material.metallicTexture); + addTextureToList(material.emissiveTexture); + addTextureToList(material.occlusionTexture); + addTextureToList(material.scatteringTexture); + addTextureToList(material.lightmapTexture); + } + + foreach(const auto& materialMapping, model->getMaterialMapping()) { + foreach(const auto& networkMaterial, materialMapping.second.data()->parsedMaterials.networkMaterials) { + foreach(const auto& textureMap, networkMaterial.second->getTextureMaps()) { + texturesFound++; + } + } + } + + auto normalizedURL = DependencyManager::get()->normalizeURL( + QUrl(avatarModel.originalURL)).resolved(QUrl("textures")); + + bool isTextureFolderEmpty = true; + if (normalizedURL.isLocalFile()) { + QDir texturesFolder(normalizedURL.toLocalFile()); + if (texturesFolder.exists()) { + isTextureFolderEmpty = texturesFolder.isEmpty(); + } + } + + if (texturesFound == 0 && !isTextureFolderEmpty) { + _errors.push_back({ tr("No textures assigned."), DEFAULT_URL }); + } + + if (!externalTextures.empty()) { + // Check External Textures: + auto modelTexturesURLs = model->getTextures(); + _externalTextureCount = externalTextures.length(); + foreach(const QString textureKey, externalTextures) { + if (!modelTexturesURLs.contains(textureKey)) { + _missingTextureCount++; + _checkedTextureCount++; + continue; + } + + const QUrl textureURL = modelTexturesURLs[textureKey].toUrl(); + + auto textureResource = DependencyManager::get()->getTexture(textureURL); + auto checkTextureLoadingComplete = [this]() mutable { + qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount; + + if (_checkedTextureCount == _externalTextureCount) { + if (_missingTextureCount > 0) { + _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL }); + } + if (_unsupportedTextureCount > 0) { + _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), + DEFAULT_URL }); + } + + emit complete(getErrors()); + } + }; + + auto textureLoaded = [this, textureResource, checkTextureLoadingComplete](bool success) mutable { + if (!success) { + auto normalizedURL = DependencyManager::get()->normalizeURL(textureResource->getURL()); + if (normalizedURL.isLocalFile()) { + QFile textureFile(normalizedURL.toLocalFile()); + if (textureFile.exists()) { + _unsupportedTextureCount++; + } else { + _missingTextureCount++; + } + } else { + _missingTextureCount++; + } + } + _checkedTextureCount++; + checkTextureLoadingComplete(); + }; + + if (textureResource) { + textureResource->refresh(); + if (textureResource->isLoaded()) { + textureLoaded(!textureResource->isFailed()); + } else { + connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded); + } + } else { + _missingTextureCount++; + _checkedTextureCount++; + checkTextureLoadingComplete(); + } + } + } else { + emit complete(getErrors()); + } +} + QVariantList AvatarDoctor::getErrors() const { QVariantList result; for (const auto& error : _errors) { diff --git a/interface/src/avatar/AvatarDoctor.h b/interface/src/avatar/AvatarDoctor.h index bebec32542..17397d99df 100644 --- a/interface/src/avatar/AvatarDoctor.h +++ b/interface/src/avatar/AvatarDoctor.h @@ -16,6 +16,7 @@ #include #include #include +#include struct AvatarDiagnosticResult { QString message; @@ -24,10 +25,25 @@ struct AvatarDiagnosticResult { Q_DECLARE_METATYPE(AvatarDiagnosticResult) Q_DECLARE_METATYPE(QVector) + +class DiagnosableAvatar: public Avatar { +public: + explicit DiagnosableAvatar(QThread* thread); + virtual ~DiagnosableAvatar(); + + void simulate(float deltaTime, bool inView) override { + + } + void rebuildCollisionShape() override { + + } + virtual void instantiableAvatar() override { }; +}; + class AvatarDoctor : public QObject { Q_OBJECT public: - AvatarDoctor(QUrl avatarFSTFileUrl); + AvatarDoctor(const QUrl& avatarFSTFileUrl); Q_INVOKABLE void startDiagnosing(); @@ -37,6 +53,8 @@ signals: void complete(QVariantList errors); private: + void diagnoseTextures(); + QUrl _avatarFSTFileUrl; QVector _errors; @@ -45,6 +63,13 @@ private: int _missingTextureCount = 0; int _unsupportedTextureCount = 0; + int _materialMappingCount = 0; + int _materialMappingLoadedCount = 0; + + DiagnosableAvatar* _avatar { nullptr }; + + GeometryResource::Pointer _model; + bool _isDiagnosing = false; }; From d985d1bff0ee5221986d0d4526c7e8cb586e71c8 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Mon, 4 Mar 2019 19:59:29 +0100 Subject: [PATCH 065/132] use Rig instead of Avatar for height calculations --- interface/src/avatar/AvatarDoctor.cpp | 99 +++++++++---------- interface/src/avatar/AvatarDoctor.h | 19 +--- libraries/animation/src/Rig.cpp | 49 +++++++++ libraries/animation/src/Rig.h | 3 + .../src/avatars-renderer/Avatar.cpp | 49 +-------- 5 files changed, 99 insertions(+), 120 deletions(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index 66433f005b..f15c8bbc6c 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -12,8 +12,9 @@ #include "AvatarDoctor.h" #include #include +#include #include - +#include #include const int NETWORKED_JOINTS_LIMIT = 256; @@ -56,18 +57,6 @@ static QStringList HAND_MAPPING_SUFFIXES = { const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); -DiagnosableAvatar::DiagnosableAvatar(QThread* thread) : Avatar(thread) { - // give the pointer to our head to inherited _headData variable from AvatarData - _headData = new Head(this); - _skeletonModel = std::make_shared(this, nullptr); - _skeletonModel->setLoadingPriority(MYAVATAR_LOADING_PRIORITY); - connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); - connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); - connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); -} - -DiagnosableAvatar::~DiagnosableAvatar() = default; - AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) : _avatarFSTFileUrl(avatarFSTFileUrl) { @@ -178,59 +167,61 @@ void AvatarDoctor::startDiagnosing() { if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) { _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); } - _avatar = new DiagnosableAvatar(QThread::currentThread()); - _avatar->setSkeletonModelURL(_avatarFSTFileUrl); - if (_avatar->getSkeletonModel()->updateGeometry()) { - // Rig has been fully loaded - // SCALE - const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; - const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; - const float avatarHeight = _avatar->getHeight(); - if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { - _errors.push_back({ "Avatar is possibly too short.", DEFAULT_URL }); - } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { - _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_URL }); } - auto rig = &_avatar->getSkeletonModel()->getRig(); - // HipsNotOnGround - auto hipsIndex = rig->indexOfJoint("Hips"); - if (hipsIndex >= 0) { - glm::vec3 hipsPosition; - if (rig->getJointPosition(hipsIndex, hipsPosition)) { - const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips")); + const auto rig = new Rig(); + rig->reset(avatarModel); + const float eyeHeight = rig->getUnscaledEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + const float avatarHeight = eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; + qDebug() << "avatarHeight = " << avatarHeight; - if (hipsPosition.y < HIPS_GROUND_MIN_Y) { - _errors.push_back({ "Hips are on ground.", DEFAULT_URL }); - } - } - } + // SCALE + const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; + const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; - // HipsSpineChestNotCoincident - auto spineIndex = rig->indexOfJoint("Spine"); - auto chestIndex = rig->indexOfJoint("Spine1"); - if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) { - glm::vec3 hipsPosition; - glm::vec3 spinePosition; - glm::vec3 chestPosition; - if (rig->getJointPosition(hipsIndex, hipsPosition) && - rig->getJointPosition(spineIndex, spinePosition) && - rig->getJointPosition(chestIndex, chestPosition)) { + if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { + _errors.push_back({ "Avatar is possibly too short.", DEFAULT_URL }); + } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { + _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_URL }); + } - const auto hipsToSpine = glm::length(hipsPosition - spinePosition); - const auto spineToChest = glm::length(spinePosition - chestPosition); - if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) { - _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_URL }); - } + // HipsNotOnGround + auto hipsIndex = rig->indexOfJoint("Hips"); + if (hipsIndex >= 0) { + glm::vec3 hipsPosition; + if (rig->getJointPosition(hipsIndex, hipsPosition)) { + const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips")); + + if (hipsPosition.y < HIPS_GROUND_MIN_Y) { + _errors.push_back({ "Hips are on ground.", DEFAULT_URL }); } } } - _avatar->deleteLater(); - _avatar = nullptr; + + // HipsSpineChestNotCoincident + auto spineIndex = rig->indexOfJoint("Spine"); + auto chestIndex = rig->indexOfJoint("Spine1"); + if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) { + glm::vec3 hipsPosition; + glm::vec3 spinePosition; + glm::vec3 chestPosition; + if (rig->getJointPosition(hipsIndex, hipsPosition) && + rig->getJointPosition(spineIndex, spinePosition) && + rig->getJointPosition(chestIndex, chestPosition)) { + + const auto hipsToSpine = glm::length(hipsPosition - spinePosition); + const auto spineToChest = glm::length(spinePosition - chestPosition); + if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) { + _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_URL }); + } + } + } + rig->deleteLater(); auto mapping = resource->getMapping(); diff --git a/interface/src/avatar/AvatarDoctor.h b/interface/src/avatar/AvatarDoctor.h index 17397d99df..780f600bed 100644 --- a/interface/src/avatar/AvatarDoctor.h +++ b/interface/src/avatar/AvatarDoctor.h @@ -16,7 +16,7 @@ #include #include #include -#include +#include "GeometryCache.h" struct AvatarDiagnosticResult { QString message; @@ -25,21 +25,6 @@ struct AvatarDiagnosticResult { Q_DECLARE_METATYPE(AvatarDiagnosticResult) Q_DECLARE_METATYPE(QVector) - -class DiagnosableAvatar: public Avatar { -public: - explicit DiagnosableAvatar(QThread* thread); - virtual ~DiagnosableAvatar(); - - void simulate(float deltaTime, bool inView) override { - - } - void rebuildCollisionShape() override { - - } - virtual void instantiableAvatar() override { }; -}; - class AvatarDoctor : public QObject { Q_OBJECT public: @@ -66,8 +51,6 @@ private: int _materialMappingCount = 0; int _materialMappingLoadedCount = 0; - DiagnosableAvatar* _avatar { nullptr }; - GeometryResource::Pointer _model; bool _isDiagnosing = false; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0fe03c7074..07bdfde189 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -2179,3 +2179,52 @@ void Rig::initFlow(bool isActive) { _networkFlow.cleanUp(); } } + +float Rig::getUnscaledEyeHeight() const { + // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. + AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), getModelOffsetPose().rot(), getModelOffsetPose().trans()); + AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * getGeometryOffsetPose(); + + // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. + // Typically it will be the unit conversion from cm to m. + float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + + int headTopJoint = indexOfJoint("HeadTop_End"); + int headJoint = indexOfJoint("Head"); + int eyeJoint = indexOfJoint("LeftEye") != -1 ? indexOfJoint("LeftEye") : indexOfJoint("RightEye"); + int toeJoint = indexOfJoint("LeftToeBase") != -1 ? indexOfJoint("LeftToeBase") : indexOfJoint("RightToeBase"); + + // Makes assumption that the y = 0 plane in geometry is the ground plane. + // We also make that assumption in Rig::computeAvatarBoundingCapsule() + const float GROUND_Y = 0.0f; + + // Values from the skeleton are in the geometry coordinate frame. + auto skeleton = getAnimSkeleton(); + if (eyeJoint >= 0 && toeJoint >= 0) { + // Measure from eyes to toes. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * eyeHeight; + } else if (eyeJoint >= 0) { + // Measure Eye joint to y = 0 plane. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; + return scaleFactor * eyeHeight; + } else if (headTopJoint >= 0 && toeJoint >= 0) { + // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. + const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; + float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * (height - height * ratio); + } else if (headTopJoint >= 0) { + // Measure from HeadTop_End joint to the ground, then remove forehead distance. + const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; + float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; + return scaleFactor * (headHeight - headHeight * ratio); + } else if (headJoint >= 0) { + // Measure Head joint to the ground, then add in distance from neck to eye. + const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; + const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; + float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; + return scaleFactor * (neckHeight + neckHeight * ratio); + } else { + return DEFAULT_AVATAR_EYE_HEIGHT; + } +} diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 2f0e2ad65b..df13ff5c2b 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -26,6 +26,7 @@ #include "SimpleMovingAverage.h" #include "AnimUtil.h" #include "Flow.h" +#include "AvatarConstants.h" class Rig; class AnimInverseKinematics; @@ -237,6 +238,8 @@ public: void initFlow(bool isActive); Flow& getFlow() { return _internalFlow; } + float getUnscaledEyeHeight() const; + signals: void onLoadComplete(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d3ae030296..f3e671143b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -2040,54 +2040,7 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const { // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. if (_skeletonModel) { - auto& rig = _skeletonModel->getRig(); - - // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. - AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); - AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose(); - - // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. - // Typically it will be the unit conversion from cm to m. - float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. - - int headTopJoint = rig.indexOfJoint("HeadTop_End"); - int headJoint = rig.indexOfJoint("Head"); - int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye"); - int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase"); - - // Makes assumption that the y = 0 plane in geometry is the ground plane. - // We also make that assumption in Rig::computeAvatarBoundingCapsule() - const float GROUND_Y = 0.0f; - - // Values from the skeleton are in the geometry coordinate frame. - auto skeleton = rig.getAnimSkeleton(); - if (eyeJoint >= 0 && toeJoint >= 0) { - // Measure from eyes to toes. - float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; - return scaleFactor * eyeHeight; - } else if (eyeJoint >= 0) { - // Measure Eye joint to y = 0 plane. - float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; - return scaleFactor * eyeHeight; - } else if (headTopJoint >= 0 && toeJoint >= 0) { - // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. - const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; - return scaleFactor * (height - height * ratio); - } else if (headTopJoint >= 0) { - // Measure from HeadTop_End joint to the ground, then remove forehead distance. - const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; - return scaleFactor * (headHeight - headHeight * ratio); - } else if (headJoint >= 0) { - // Measure Head joint to the ground, then add in distance from neck to eye. - const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; - const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; - float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; - return scaleFactor * (neckHeight + neckHeight * ratio); - } else { - return DEFAULT_AVATAR_EYE_HEIGHT; - } + return _skeletonModel->getRig().getUnscaledEyeHeight(); } else { return DEFAULT_AVATAR_EYE_HEIGHT; } From c35e4d15ab8252f0ff46100bbe34b6f3f78065e9 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Wed, 6 Mar 2019 07:20:12 +0100 Subject: [PATCH 066/132] - warning grammar / no more texture folder content requirement for No textures assigned warning - resolved partial commit issue / Fix UNIX builds by removal of unused warning --- interface/src/avatar/AvatarDoctor.cpp | 29 ++++++++------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index f15c8bbc6c..45d05ad608 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -168,11 +168,6 @@ void AvatarDoctor::startDiagnosing() { _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); } - - - } - - const auto rig = new Rig(); rig->reset(avatarModel); const float eyeHeight = rig->getUnscaledEyeHeight(); @@ -241,15 +236,15 @@ void AvatarDoctor::startDiagnosing() { } if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) { - _errors.push_back({ "Spine is no child of Hips.", DEFAULT_URL }); + _errors.push_back({ "Spine is not a child of Hips.", DEFAULT_URL }); } if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) { - _errors.push_back({ "Spine1 is no child of Spine.", DEFAULT_URL }); + _errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_URL }); } if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) { - _errors.push_back({ "Head is no child of Spine1.", DEFAULT_URL }); + _errors.push_back({ "Head is not a child of Spine1.", DEFAULT_URL }); } } @@ -272,7 +267,9 @@ void AvatarDoctor::startDiagnosing() { if (materialMappingResource->isLoaded()) { materialMappingHandled(); } else { - connect(materialMappingResource.data(), &NetworkTexture::finished, this, [materialMappingHandled](bool success) mutable { + connect(materialMappingResource.data(), &NetworkTexture::finished, this, + [materialMappingHandled](bool success) mutable { + materialMappingHandled(); }); } @@ -330,24 +327,14 @@ void AvatarDoctor::diagnoseTextures() { foreach(const auto& materialMapping, model->getMaterialMapping()) { foreach(const auto& networkMaterial, materialMapping.second.data()->parsedMaterials.networkMaterials) { - foreach(const auto& textureMap, networkMaterial.second->getTextureMaps()) { - texturesFound++; - } + texturesFound += (int)networkMaterial.second->getTextureMaps().size(); } } auto normalizedURL = DependencyManager::get()->normalizeURL( QUrl(avatarModel.originalURL)).resolved(QUrl("textures")); - bool isTextureFolderEmpty = true; - if (normalizedURL.isLocalFile()) { - QDir texturesFolder(normalizedURL.toLocalFile()); - if (texturesFolder.exists()) { - isTextureFolderEmpty = texturesFolder.isEmpty(); - } - } - - if (texturesFound == 0 && !isTextureFolderEmpty) { + if (texturesFound == 0) { _errors.push_back({ tr("No textures assigned."), DEFAULT_URL }); } From f2bca5d6c6b94d3c749844a8f375ed15a81b419a Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 8 Mar 2019 07:32:41 +0100 Subject: [PATCH 067/132] added the multiple root joints warning --- interface/src/avatar/AvatarDoctor.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index 45d05ad608..8c22780b52 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -168,12 +168,23 @@ void AvatarDoctor::startDiagnosing() { _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); } + // Multiple skeleton root joints checkup + int skeletonRootJoints = 0; + foreach(const HFMJoint& joint, avatarModel.joints) { + if (joint.parentIndex == -1 && joint.isSkeletonJoint) { + skeletonRootJoints++; + } + } + + if (skeletonRootJoints > 1) { + _errors.push_back({ "Multiple root joints found.", DEFAULT_URL }); + } + const auto rig = new Rig(); rig->reset(avatarModel); const float eyeHeight = rig->getUnscaledEyeHeight(); const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; const float avatarHeight = eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; - qDebug() << "avatarHeight = " << avatarHeight; // SCALE const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; From 571e7c1ecc1af5c7a0475ec7ee33c709a69bb5ce Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 8 Mar 2019 09:20:19 -0800 Subject: [PATCH 068/132] update avatar exporter package --- .../avatarExporter.unitypackage | Bin 15813 -> 15811 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index fd9c06f21233a8974171617d2a393e5ac8f4c6f1..c60a156711329e88a09234efca428eb3ceb6e89d 100644 GIT binary patch delta 15003 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 l;8mQ>b{4%|h>$8IK@_rTG+@_S|y@Nl! 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 From e8da6b5a0cf9c388f70ae9c04fabe26601fff944 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 10:36:58 -0700 Subject: [PATCH 069/132] add getFlowData --- interface/src/avatar/MyAvatar.cpp | 57 ++++++++++++++++++++++++++++++- interface/src/avatar/MyAvatar.h | 2 ++ libraries/animation/src/Flow.cpp | 10 ++++++ libraries/animation/src/Flow.h | 4 +++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index faa9f88ae9..8d499cd8db 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5331,9 +5331,13 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { if (_skeletonModel->isLoaded()) { - _skeletonModel->getRig().initFlow(isActive); auto &flow = _skeletonModel->getRig().getFlow(); auto &collisionSystem = flow.getCollisionSystem(); + if (!flow.isInitialized() && isActive) { + _skeletonModel->getRig().initFlow(true); + } else { + flow.setActive(isActive); + } collisionSystem.setActive(isCollidable); auto physicsGroups = physicsConfig.keys(); if (physicsGroups.size() > 0) { @@ -5384,6 +5388,57 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys } } +QVariantMap MyAvatar::getFlowData() { + QVariantMap flowData; + if (_skeletonModel->isLoaded()) { + auto jointNames = getJointNames(); + auto &flow = _skeletonModel->getRig().getFlow(); + auto &collisionSystem = flow.getCollisionSystem(); + bool initialized = flow.isInitialized(); + flowData.insert("initialized", initialized); + flowData.insert("active", flow.getActive()); + flowData.insert("colliding", collisionSystem.getActive()); + QVariantMap physicsData; + QVariantMap collisionsData; + QVariantMap threadData; + auto &groups = flow.getGroupSettings(); + for (auto &group : groups) { + QVariantMap settingsObject; + QString groupName = group.first; + FlowPhysicsSettings groupSettings = group.second; + settingsObject.insert("active", groupSettings._active); + settingsObject.insert("damping", groupSettings._damping); + settingsObject.insert("delta", groupSettings._delta); + settingsObject.insert("gravity", groupSettings._gravity); + settingsObject.insert("inertia", groupSettings._inertia); + settingsObject.insert("radius", groupSettings._radius); + settingsObject.insert("stiffness", groupSettings._stiffness); + physicsData.insert(groupName, settingsObject); + } + auto &collisions = collisionSystem.getCollisions(); + for (auto &collision : collisions) { + QVariantMap collisionObject; + collisionObject.insert("offset", vec3toVariant(collision._offset)); + collisionObject.insert("radius", collision._radius); + collisionObject.insert("jointIndex", collision._jointIndex); + QString jointName = jointNames.size() > collision._jointIndex ? jointNames[collision._jointIndex] : "unknown"; + collisionsData.insert(jointName, collisionObject); + } + int count = 0; + for (auto &thread : flow.getThreads()) { + QVariantList indices; + for (int index : thread._joints) { + indices.append(index); + } + threadData.insert(thread._jointsPointer->at(thread._joints[0]).getName(), indices); + } + flowData.insert("physics", physicsData); + flowData.insert("collisions", collisionsData); + flowData.insert("threads", threadData); + } + return flowData; +} + void MyAvatar::sendPacket(const QUuid& entityID, const EntityItemProperties& properties) const { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 2c8dedd430..26aea8bf6e 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1198,6 +1198,8 @@ public: */ Q_INVOKABLE void useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap()); + Q_INVOKABLE QVariantMap getFlowData(); + public slots: /**jsdoc diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 3bb80b9375..ddc3354a2f 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -509,6 +509,7 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton, auto flowJoint = FlowJoint(i, parentIndex, -1, name, group, jointSettings); _flowJointData.insert(std::pair(i, flowJoint)); } + updateGroupSettings(group, jointSettings); } } else { if (PRESET_COLLISION_DATA.find(name) != PRESET_COLLISION_DATA.end()) { @@ -727,6 +728,7 @@ void Flow::setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSet joint.second.setSettings(settings); } } + updateGroupSettings(group, settings); } bool Flow::getJointPositionInWorldFrame(const AnimPoseVec& absolutePoses, int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { @@ -780,4 +782,12 @@ Flow& Flow::operator=(const Flow& otherFlow) { } } return *this; +} + +void Flow::updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings) { + if (_groupSettings.find(group) != _groupSettings.end()) { + _groupSettings.insert(std::pair(group, settings)); + } else { + _groupSettings[group] = settings; + } } \ No newline at end of file diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index 35464e9420..d2eaaa22d9 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -149,6 +149,7 @@ public: void setCollisionSettingsByJoint(int jointIndex, const FlowCollisionSettings& settings); void setActive(bool active) { _active = active; } bool getActive() const { return _active; } + const std::vector& getCollisions() const { return _selfCollisions; } protected: std::vector _selfCollisions; std::vector _othersCollisions; @@ -293,6 +294,7 @@ public: void setOthersCollision(const QUuid& otherId, int jointIndex, const glm::vec3& position); FlowCollisionSystem& getCollisionSystem() { return _collisionSystem; } void setPhysicsSettingsForGroup(const QString& group, const FlowPhysicsSettings& settings); + const std::map& getGroupSettings() const { return _groupSettings; } void cleanUp(); signals: @@ -309,6 +311,7 @@ private: void setJoints(AnimPoseVec& relativePoses, const std::vector& overrideFlags); void updateJoints(AnimPoseVec& relativePoses, AnimPoseVec& absolutePoses); bool updateRootFramePositions(const AnimPoseVec& absolutePoses, size_t threadIndex); + void updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings); void setScale(float scale); float _scale { 1.0f }; @@ -316,6 +319,7 @@ private: glm::vec3 _entityPosition; glm::quat _entityRotation; std::map _flowJointData; + std::map _groupSettings; std::vector _jointThreads; std::vector _flowJointKeywords; FlowCollisionSystem _collisionSystem; From 5fd2b0699ab61023ce9c494c627bdadfe0fb4a4c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 7 Mar 2019 22:44:29 -0800 Subject: [PATCH 070/132] Add more complete support for .tga files --- libraries/image/src/image/Image.cpp | 12 ++ libraries/image/src/image/TGAReader.cpp | 186 ++++++++++++++++++++++++ libraries/image/src/image/TGAReader.h | 23 +++ 3 files changed, 221 insertions(+) create mode 100644 libraries/image/src/image/TGAReader.cpp create mode 100644 libraries/image/src/image/TGAReader.h diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index a2161caec9..6aa09c4d0f 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -25,6 +25,8 @@ #include #include +#include "TGAReader.h" + #include "ImageLogging.h" using namespace gpu; @@ -203,6 +205,16 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) { // Help the QImage loader by extracting the image file format from the url filename ext. // Some tga are not created properly without it. auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); + content.open(QIODevice::ReadOnly); + + if (filenameExtension == "tga") { + QImage image = image::readTGA(content); + if (!image.isNull()) { + return image; + } + content.reset(); + } + QImageReader imageReader(&content, filenameExtension.c_str()); if (imageReader.canRead()) { diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp new file mode 100644 index 0000000000..a496faf9e4 --- /dev/null +++ b/libraries/image/src/image/TGAReader.cpp @@ -0,0 +1,186 @@ +// +// TGAReader.cpp +// image/src/image +// +// Created by Ryan Huffman +// Copyright 2019 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 "TGAReader.h" + +#include +#include + +QImage image::readTGA(QIODevice& content) { + enum class TGAImageType : uint8_t { + NoImageData = 0, + UncompressedColorMapped = 1, + UncompressedTrueColor = 2, + UncompressedBlackWhite = 3, + RunLengthEncodedColorMapped = 9, + RunLengthEncodedTrueColor = 10, + RunLengthEncodedBlackWhite = 11, + }; + + struct TGAHeader { + uint8_t idLength; + uint8_t colorMapType; + TGAImageType imageType; + struct { + uint64_t firstEntryIndex : 16; + uint64_t length : 16; + uint64_t entrySize : 8; + } colorMap; + uint16_t xOrigin; + uint16_t yOrigin; + uint16_t width; + uint16_t height; + uint8_t pixelDepth; + struct { + uint8_t attributeBitsPerPixel : 4; + uint8_t orientation : 2; + uint8_t padding : 2; + } imageDescriptor; + }; + + constexpr bool WANT_DEBUG { false }; + constexpr size_t TGA_HEADER_SIZE_BYTES { 18 }; + + TGAHeader header; + + if (content.isSequential()) { + qWarning() << "TGA - Sequential devices are not supported for reading"; + return QImage(); + } + + if (content.bytesAvailable() < TGA_HEADER_SIZE_BYTES) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + + content.read((char*)&header.idLength, 1); + content.read((char*)&header.colorMapType, 1); + content.read((char*)&header.imageType, 1); + content.read((char*)&header.colorMap, 5); + content.read((char*)&header.xOrigin, 2); + content.read((char*)&header.yOrigin, 2); + content.read((char*)&header.width, 2); + content.read((char*)&header.height, 2); + content.read((char*)&header.pixelDepth, 1); + content.read((char*)&header.imageDescriptor, 1); + + if (WANT_DEBUG) { + qDebug() << "Id Length: " << (int)header.idLength; + qDebug() << "Color map: " << (int)header.colorMap.firstEntryIndex << header.colorMap.length << header.colorMap.entrySize; + qDebug() << "Color map type: " << (int)header.colorMapType; + qDebug() << "Image type: " << (int)header.imageType; + qDebug() << "Origin: " << header.xOrigin << header.yOrigin; + qDebug() << "Size: " << header.width << header.height; + qDebug() << "Depth: " << header.pixelDepth; + qDebug() << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << header.imageDescriptor.orientation; + } + + if (header.xOrigin != 0 || header.yOrigin != 0) { + qWarning() << "TGA - origin not supporter"; + return QImage(); + } + + if (!(header.pixelDepth == 24 && header.imageDescriptor.attributeBitsPerPixel == 0) && header.pixelDepth != 32) { + qWarning() << "TGA - Only pixel depths of 24 (with no alpha) and 32 bits are supported"; + return QImage(); + } + + if (header.imageDescriptor.attributeBitsPerPixel != 0 && header.imageDescriptor.attributeBitsPerPixel != 8) { + qWarning() << "TGA - Only 0 or 8 bits for the alpha channel is supported"; + return QImage(); + } + + char alphaMask = header.imageDescriptor.attributeBitsPerPixel == 8 ? 0x00 : 0xFF; + int bytesPerPixel = header.pixelDepth / 8; + + content.skip(header.idLength); + if (header.imageType == TGAImageType::UncompressedTrueColor) { + qint64 minimumSize = header.width * header.height * bytesPerPixel; + if (content.bytesAvailable() < minimumSize) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + + QImage image{ header.width, header.height, QImage::Format_ARGB32 }; + for (int y = 0; y < header.height; ++y) { + char* line = (char*)image.scanLine(y); + for (int x = 0; x < header.width; ++x) { + content.read(line, bytesPerPixel); + *(line + 3) |= alphaMask; + + line += 4; + } + } + return image; + } else if (header.imageType == TGAImageType::RunLengthEncodedTrueColor) { + QImage image{ header.width, header.height, QImage::Format_ARGB32 }; + + for (int y = 0; y < header.height; ++y) { + char* line = (char*)image.scanLine(y); + int col = 0; + while (col < header.width) { + constexpr char IS_REPETITION_MASK{ (char)0x80 }; + constexpr char LENGTH_MASK{ (char)0x7f }; + char repetition; + if (content.read(&repetition, 1) != 1) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + bool isRepetition = repetition & IS_REPETITION_MASK; + + // The length in `repetition` is always 1 less than the number of following pixels, + // so we need to increment it by 1. Because of this, the length is never 0. + int length = (repetition & LENGTH_MASK) + 1; + + if (isRepetition) { + // Read into temporary buffer + char color[4]; + if (content.read(color, bytesPerPixel) != bytesPerPixel) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + color[3] |= alphaMask; + + // Copy `length` number of times + col += length; + while (length-- > 0) { + *line = color[0]; + *(line + 1) = color[1]; + *(line + 2) = color[2]; + *(line + 3) = color[3]; + + line += 4; + } + } else { + qint64 minimumSize = length * bytesPerPixel; + if (content.bytesAvailable() < minimumSize) { + qWarning() << "TGA - Unexpectedly reached end of file"; + return QImage(); + } + + // Read in `length` number of pixels + col += length; + while (length-- > 0) { + content.read(line, bytesPerPixel); + *(line + 3) |= alphaMask; + + line += 4; + } + } + } + } + return image; + } else { + qWarning() << "TGA - Unsupported image type: " << (int)header.imageType; + } + + return QImage(); +} diff --git a/libraries/image/src/image/TGAReader.h b/libraries/image/src/image/TGAReader.h new file mode 100644 index 0000000000..11e7678264 --- /dev/null +++ b/libraries/image/src/image/TGAReader.h @@ -0,0 +1,23 @@ +// +// TGAReader.h +// image/src/image +// +// Created by Ryan Huffman +// Copyright 2019 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_image_TGAReader_h +#define hifi_image_TGAReader_h + +#include + +namespace image { + +QImage readTGA(QIODevice& contents); + +} + +#endif // hifi_image_TGAReader_h From c55811ced521d3c0582ccde5155842d0339374ff Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 8 Mar 2019 09:36:20 -0800 Subject: [PATCH 071/132] Add support for TGA orientation --- libraries/image/src/image/TGAReader.cpp | 23 +++++++++++++++++++---- libraries/image/src/image/TGAReader.h | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index a496faf9e4..a230185b49 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -24,6 +24,10 @@ QImage image::readTGA(QIODevice& content) { RunLengthEncodedTrueColor = 10, RunLengthEncodedBlackWhite = 11, }; + enum class TGAOrientation : uint8_t { + BottomLeft = 0, + TopLeft = 1, + }; struct TGAHeader { uint8_t idLength; @@ -41,7 +45,8 @@ QImage image::readTGA(QIODevice& content) { uint8_t pixelDepth; struct { uint8_t attributeBitsPerPixel : 4; - uint8_t orientation : 2; + uint8_t reserved : 1; + TGAOrientation orientation : 1; uint8_t padding : 2; } imageDescriptor; }; @@ -80,7 +85,7 @@ QImage image::readTGA(QIODevice& content) { qDebug() << "Origin: " << header.xOrigin << header.yOrigin; qDebug() << "Size: " << header.width << header.height; qDebug() << "Depth: " << header.pixelDepth; - qDebug() << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << header.imageDescriptor.orientation; + qDebug() << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << (int)header.imageDescriptor.orientation; } if (header.xOrigin != 0 || header.yOrigin != 0) { @@ -110,8 +115,13 @@ QImage image::readTGA(QIODevice& content) { } QImage image{ header.width, header.height, QImage::Format_ARGB32 }; + char* line; for (int y = 0; y < header.height; ++y) { - char* line = (char*)image.scanLine(y); + if (header.imageDescriptor.orientation == TGAOrientation::BottomLeft) { + line = (char*)image.scanLine(header.height - y - 1); + } else { + line = (char*)image.scanLine(y); + } for (int x = 0; x < header.width; ++x) { content.read(line, bytesPerPixel); *(line + 3) |= alphaMask; @@ -124,7 +134,12 @@ QImage image::readTGA(QIODevice& content) { QImage image{ header.width, header.height, QImage::Format_ARGB32 }; for (int y = 0; y < header.height; ++y) { - char* line = (char*)image.scanLine(y); + char* line; + if (header.imageDescriptor.orientation == TGAOrientation::BottomLeft) { + line = (char*)image.scanLine(header.height - y - 1); + } else { + line = (char*)image.scanLine(y); + } int col = 0; while (col < header.width) { constexpr char IS_REPETITION_MASK{ (char)0x80 }; diff --git a/libraries/image/src/image/TGAReader.h b/libraries/image/src/image/TGAReader.h index 11e7678264..8019be7f0b 100644 --- a/libraries/image/src/image/TGAReader.h +++ b/libraries/image/src/image/TGAReader.h @@ -16,6 +16,7 @@ namespace image { +// TODO Move this into a plugin that QImageReader can use QImage readTGA(QIODevice& contents); } From 750dbfae21785da6c0a9757b44cd117f0bd9fca2 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 8 Mar 2019 10:48:42 -0800 Subject: [PATCH 072/132] Stand-alone Tags: Throw up popup for first unoptimized domain Throw up a popup in stand-alone builds when user attempts to go to an unoptimized domain. --- interface/resources/qml/hifi/Card.qml | 2 +- .../hifi/commerce/marketplace/Marketplace.qml | 4 +- .../resources/qml/hifi/tablet/TADLightbox.qml | 144 ++++++++++++++++++ .../qml/hifi/tablet/TabletAddressDialog.qml | 47 ++++-- interface/src/Application.cpp | 1 + 5 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 interface/resources/qml/hifi/tablet/TADLightbox.qml diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 1c0424a691..fc49bcf048 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -269,7 +269,7 @@ Item { hoverEnabled: false onClicked: { Tablet.playSound(TabletEnums.ButtonClick); - goFunction("hifi://" + hifiUrl); + goFunction("hifi://" + hifiUrl, standaloneOptimized); } } diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 2206dfcb99..9c5e1aa898 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -92,11 +92,11 @@ Rectangle { }); categoriesModel.append({ id: -1, - name: "Standalone Optimized" + name: "Stand-alone Optimized" }); categoriesModel.append({ id: -1, - name: "Standalone Compatible" + name: "Stand-alone Compatible" }); result.data.items.forEach(function(category) { categoriesModel.append({ diff --git a/interface/resources/qml/hifi/tablet/TADLightbox.qml b/interface/resources/qml/hifi/tablet/TADLightbox.qml new file mode 100644 index 0000000000..35a01aeec3 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/TADLightbox.qml @@ -0,0 +1,144 @@ +// +// TADLightbox.qml +// qml/hifi/tablet +// +// TADLightbox +// +// Created by Roxanne Skelly on 2019-03-07 +// Copyright 2019 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 stylesUit 1.0 +import controlsUit 1.0 as HifiControlsUit +import "qrc:////qml//controls" as HifiControls + +// references XXX from root context + +Rectangle { + property string titleText; + property string bodyImageSource; + property string bodyText; + property string button1color: hifi.buttons.noneBorderlessGray; + property string button1text; + property var button1method; + property string button2color: hifi.buttons.blue; + property string button2text; + property var button2method; + property string buttonLayout: "leftright"; + + id: root; + visible: false; + anchors.fill: parent; + color: Qt.rgba(0, 0, 0, 0.5); + z: 999; + + HifiConstants { id: hifi; } + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + + Rectangle { + anchors.centerIn: parent; + width: 376; + height: childrenRect.height + 30; + color: "white"; + + RalewaySemiBold { + id: titleText; + text: root.titleText; + anchors.top: parent.top; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.right: parent.right; + anchors.rightMargin: 30; + height: paintedHeight; + color: hifi.colors.black; + size: 24; + verticalAlignment: Text.AlignTop; + wrapMode: Text.WordWrap; + } + + RalewayRegular { + id: bodyText; + text: root.bodyText; + anchors.top: root.bodyImageSource ? bodyImage.bottom : (root.titleText ? titleText.bottom : parent.top); + anchors.topMargin: root.bodyImageSource ? 20 : (root.titleText ? 20 : 30); + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.right: parent.right; + anchors.rightMargin: 30; + height: paintedHeight; + color: hifi.colors.black; + size: 20; + verticalAlignment: Text.AlignTop; + wrapMode: Text.WordWrap; + + } + + Item { + id: buttons; + anchors.top: bodyText.bottom; + anchors.topMargin: 30; + anchors.left: parent.left; + anchors.right: parent.right; + height: root.buttonLayout === "leftright" ? 70 : 150; + + // Button 1 + HifiControlsUit.Button { + id: button1; + color: root.button1color; + colorScheme: hifi.colorSchemes.light; + anchors.top: root.buttonLayout === "leftright" ? parent.top : parent.top; + anchors.left: parent.left; + anchors.leftMargin: root.buttonLayout === "leftright" ? 30 : 10; + anchors.right: root.buttonLayout === "leftright" ? undefined : parent.right; + anchors.rightMargin: root.buttonLayout === "leftright" ? undefined : 10; + width: root.buttonLayout === "leftright" ? (root.button2text ? parent.width/2 - anchors.leftMargin*2 : parent.width - anchors.leftMargin * 2) : + (undefined); + height: 40; + text: root.button1text; + onClicked: { + button1method(); + } + visible: (root.button1text !== ""); + } + + // Button 2 + HifiControlsUit.Button { + id: button2; + visible: root.button2text; + color: root.button2color; + colorScheme: hifi.colorSchemes.light; + anchors.top: root.buttonLayout === "leftright" ? parent.top : button1.bottom; + anchors.topMargin: root.buttonLayout === "leftright" ? undefined : 20; + anchors.left: root.buttonLayout === "leftright" ? undefined : parent.left; + anchors.leftMargin: root.buttonLayout === "leftright" ? undefined : 10; + anchors.right: parent.right; + anchors.rightMargin: root.buttonLayout === "leftright" ? 30 : 10; + width: root.buttonLayout === "leftright" ? parent.width/2 - anchors.rightMargin*2 : undefined; + height: 40; + text: root.button2text; + onClicked: { + button2method(); + } + } + } + } + + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index ab0a98a8c5..311d20955b 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -73,7 +73,7 @@ StackView { function resetAfterTeleport() { //storyCardFrame.shown = root.shown = false; } - function goCard(targetString) { + function goCard(targetString, standaloneOptimized) { if (0 !== targetString.indexOf('hifi://')) { var card = tabletWebView.createObject(); card.url = addressBarDialog.metaverseServerUrl + targetString; @@ -82,7 +82,7 @@ StackView { return; } location.text = targetString; - toggleOrGo(targetString, true); + toggleOrGo(targetString, true, standaloneOptimized); clearAddressLineTimer.start(); } @@ -392,7 +392,18 @@ StackView { right: parent.right } } + } + TADLightbox { + id: unoptimizedDomain + titleText: "Unoptimized Domain" + bodyText: "You're trying to access a place that hasn't been optimized for your device. Are you sure you want to continue." + button1text: "CANCEL" + button2text: "YES CONTINUE" + visible: false + button1method: function() { + visible = false; + } } function updateLocationText(enteringAddress) { @@ -407,14 +418,30 @@ StackView { } } - function toggleOrGo(address, fromSuggestions) { - if (address !== undefined && address !== "") { - addressBarDialog.loadAddress(address, fromSuggestions); - clearAddressLineTimer.start(); - } else if (addressLine.text !== "") { - addressBarDialog.loadAddress(addressLine.text, fromSuggestions); - clearAddressLineTimer.start(); + function toggleOrGo(address, fromSuggestions, standaloneOptimized) { + + var goTarget = function () { + if (address !== undefined && address !== "") { + addressBarDialog.loadAddress(address, fromSuggestions); + clearAddressLineTimer.start(); + } else if (addressLine.text !== "") { + addressBarDialog.loadAddress(addressLine.text, fromSuggestions); + clearAddressLineTimer.start(); + } + DialogsManager.hideAddressBar(); + } + + unoptimizedDomain.button2method = function() { + Settings.setValue("ShowUnoptimizedDomainWarning", false); + goTarget(); + } + + var showPopup = PlatformInfo.isStandalone() && !standaloneOptimized && Settings.getValue("ShowUnoptimizedDomainWarning", true); + + if(!showPopup) { + goTarget(); + } else { + unoptimizedDomain.visible = true; } - DialogsManager.hideAddressBar(); } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c115e4c4d3..5fd90503e3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3036,6 +3036,7 @@ void Application::initializeUi() { QUrl{ "hifi/commerce/purchases/Purchases.qml" }, QUrl{ "hifi/commerce/wallet/Wallet.qml" }, QUrl{ "hifi/commerce/wallet/WalletHome.qml" }, + QUrl{ "hifi/tablet/TabletAddressDialog.qml" }, }, platformInfoCallback); QmlContextCallback ttsCallback = [](QQmlContext* context) { From a977bb6dc8f5708b12d7fec393f5fdf14a5287a5 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 12:39:57 -0700 Subject: [PATCH 073/132] remove unuse variable --- interface/src/avatar/MyAvatar.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8d499cd8db..b596a363f4 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5424,7 +5424,6 @@ QVariantMap MyAvatar::getFlowData() { QString jointName = jointNames.size() > collision._jointIndex ? jointNames[collision._jointIndex] : "unknown"; collisionsData.insert(jointName, collisionObject); } - int count = 0; for (auto &thread : flow.getThreads()) { QVariantList indices; for (int index : thread._joints) { From cbe920774f90fc917369c2c69d9fc692e3b31f4e Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 12:45:29 -0700 Subject: [PATCH 074/132] add getFlowData jsdoc --- interface/src/avatar/MyAvatar.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 042a5c2a3c..c67cabe4f5 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1198,6 +1198,10 @@ public: */ Q_INVOKABLE void useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig = QVariantMap(), const QVariantMap& collisionsConfig = QVariantMap()); + /**jsdoc + * @function MyAvatar.getFlowData + * @returns {object} + */ Q_INVOKABLE QVariantMap getFlowData(); public slots: From 6b18b224339dae7393af260e96ee51e390c00b85 Mon Sep 17 00:00:00 2001 From: amantley Date: Wed, 6 Mar 2019 17:26:46 -0800 Subject: [PATCH 075/132] loading jointRotationOffset2 from FST and deal with the shadow joints --- .../src/model-baker/PrepareJointsTask.cpp | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index a896766058..b8bcdb386e 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -29,8 +29,14 @@ QMap getJointNameMapping(const QVariantHash& mapping) { QMap getJointRotationOffsets(const QVariantHash& mapping) { QMap jointRotationOffsets; static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; - if (!mapping.isEmpty() && mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) { - auto offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2"; + if (!mapping.isEmpty() && ((mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) || (mapping.contains(JOINT_ROTATION_OFFSET2_FIELD) && mapping[JOINT_ROTATION_OFFSET2_FIELD].type() == QVariant::Hash))) { + QHash offsets; + if (mapping.contains(JOINT_ROTATION_OFFSET_FIELD)) { + offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); + } else { + offsets = mapping[JOINT_ROTATION_OFFSET2_FIELD].toHash(); + } for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { QString jointName = itr.key(); QString line = itr.value().toString(); @@ -57,6 +63,15 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu auto& jointRotationOffsets = output.edit1(); auto& jointIndices = output.edit2(); + bool newJointRot = false; + static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2"; + QVariantHash fstHashMap = mapping.second; + if (fstHashMap.contains(JOINT_ROTATION_OFFSET2_FIELD)) { + newJointRot = true; + } else { + newJointRot = false; + } + // Get joint renames auto jointNameMapping = getJointNameMapping(mapping.second); // Apply joint metadata from FST file mappings @@ -64,11 +79,12 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu jointsOut.push_back(jointIn); auto& jointOut = jointsOut.back(); - auto jointNameMapKey = jointNameMapping.key(jointIn.name); - if (jointNameMapping.contains(jointNameMapKey)) { - jointOut.name = jointNameMapKey; + if (!newJointRot) { + auto jointNameMapKey = jointNameMapping.key(jointIn.name); + if (jointNameMapping.contains(jointNameMapKey)) { + jointOut.name = jointNameMapKey; + } } - jointIndices.insert(jointOut.name, (int)jointsOut.size()); } @@ -77,10 +93,33 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu for (auto itr = offsets.begin(); itr != offsets.end(); itr++) { QString jointName = itr.key(); int jointIndex = jointIndices.value(jointName) - 1; - if (jointIndex != -1) { + if (jointIndex >= 0) { glm::quat rotationOffset = itr.value(); jointRotationOffsets.insert(jointIndex, rotationOffset); qCDebug(model_baker) << "Joint Rotation Offset added to Rig._jointRotationOffsets : " << " jointName: " << jointName << " jointIndex: " << jointIndex << " rotation offset: " << rotationOffset; } } + + if (newJointRot) { + for (const auto& jointIn : jointsIn) { + + auto jointNameMapKey = jointNameMapping.key(jointIn.name); + int mappedIndex = jointIndices.value(jointIn.name); + if (jointNameMapping.contains(jointNameMapKey)) { + + // delete and replace with hifi name + jointIndices.remove(jointIn.name); + jointIndices.insert(jointNameMapKey, mappedIndex); + } else { + + // nothing mapped to this fbx joint name + if (jointNameMapping.contains(jointIn.name)) { + // but the name is in the list of hifi names is mapped to a different joint + int extraIndex = jointIndices.value(jointIn.name); + jointIndices.remove(jointIn.name); + jointIndices.insert("", extraIndex); + } + } + } + } } From 2af9dc886a9bc0b6ecdb91d68a0abea121cc684a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 8 Mar 2019 11:56:04 -0800 Subject: [PATCH 076/132] Add logging category to TGAReader --- libraries/image/src/image/TGAReader.cpp | 38 +++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index a230185b49..a6dd7e9c71 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -11,6 +11,8 @@ #include "TGAReader.h" +#include "ImageLogging.h" + #include #include @@ -57,12 +59,12 @@ QImage image::readTGA(QIODevice& content) { TGAHeader header; if (content.isSequential()) { - qWarning() << "TGA - Sequential devices are not supported for reading"; + qWarning(imagelogging) << "TGA - Sequential devices are not supported for reading"; return QImage(); } if (content.bytesAvailable() < TGA_HEADER_SIZE_BYTES) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } @@ -78,28 +80,28 @@ QImage image::readTGA(QIODevice& content) { content.read((char*)&header.imageDescriptor, 1); if (WANT_DEBUG) { - qDebug() << "Id Length: " << (int)header.idLength; - qDebug() << "Color map: " << (int)header.colorMap.firstEntryIndex << header.colorMap.length << header.colorMap.entrySize; - qDebug() << "Color map type: " << (int)header.colorMapType; - qDebug() << "Image type: " << (int)header.imageType; - qDebug() << "Origin: " << header.xOrigin << header.yOrigin; - qDebug() << "Size: " << header.width << header.height; - qDebug() << "Depth: " << header.pixelDepth; - qDebug() << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << (int)header.imageDescriptor.orientation; + qDebug(imagelogging) << "Id Length: " << (int)header.idLength; + qDebug(imagelogging) << "Color map: " << (int)header.colorMap.firstEntryIndex << header.colorMap.length << header.colorMap.entrySize; + qDebug(imagelogging) << "Color map type: " << (int)header.colorMapType; + qDebug(imagelogging) << "Image type: " << (int)header.imageType; + qDebug(imagelogging) << "Origin: " << header.xOrigin << header.yOrigin; + qDebug(imagelogging) << "Size: " << header.width << header.height; + qDebug(imagelogging) << "Depth: " << header.pixelDepth; + qDebug(imagelogging) << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << (int)header.imageDescriptor.orientation; } if (header.xOrigin != 0 || header.yOrigin != 0) { - qWarning() << "TGA - origin not supporter"; + qWarning(imagelogging) << "TGA - origin not supporter"; return QImage(); } if (!(header.pixelDepth == 24 && header.imageDescriptor.attributeBitsPerPixel == 0) && header.pixelDepth != 32) { - qWarning() << "TGA - Only pixel depths of 24 (with no alpha) and 32 bits are supported"; + qWarning(imagelogging) << "TGA - Only pixel depths of 24 (with no alpha) and 32 bits are supported"; return QImage(); } if (header.imageDescriptor.attributeBitsPerPixel != 0 && header.imageDescriptor.attributeBitsPerPixel != 8) { - qWarning() << "TGA - Only 0 or 8 bits for the alpha channel is supported"; + qWarning(imagelogging) << "TGA - Only 0 or 8 bits for the alpha channel is supported"; return QImage(); } @@ -110,7 +112,7 @@ QImage image::readTGA(QIODevice& content) { if (header.imageType == TGAImageType::UncompressedTrueColor) { qint64 minimumSize = header.width * header.height * bytesPerPixel; if (content.bytesAvailable() < minimumSize) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } @@ -146,7 +148,7 @@ QImage image::readTGA(QIODevice& content) { constexpr char LENGTH_MASK{ (char)0x7f }; char repetition; if (content.read(&repetition, 1) != 1) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } bool isRepetition = repetition & IS_REPETITION_MASK; @@ -159,7 +161,7 @@ QImage image::readTGA(QIODevice& content) { // Read into temporary buffer char color[4]; if (content.read(color, bytesPerPixel) != bytesPerPixel) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } color[3] |= alphaMask; @@ -177,7 +179,7 @@ QImage image::readTGA(QIODevice& content) { } else { qint64 minimumSize = length * bytesPerPixel; if (content.bytesAvailable() < minimumSize) { - qWarning() << "TGA - Unexpectedly reached end of file"; + qWarning(imagelogging) << "TGA - Unexpectedly reached end of file"; return QImage(); } @@ -194,7 +196,7 @@ QImage image::readTGA(QIODevice& content) { } return image; } else { - qWarning() << "TGA - Unsupported image type: " << (int)header.imageType; + qWarning(imagelogging) << "TGA - Unsupported image type: " << (int)header.imageType; } return QImage(); From f363d95ca2b9d66806e50cf74b1e8b8fa668f9bb Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 13:02:47 -0700 Subject: [PATCH 077/132] clear group settings on init --- libraries/animation/src/Flow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index ddc3354a2f..6e210fe71c 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -463,6 +463,7 @@ void Flow::calculateConstraints(const std::shared_ptr& skeleton, auto flowPrefix = FLOW_JOINT_PREFIX.toUpper(); auto simPrefix = SIM_JOINT_PREFIX.toUpper(); std::vector handsIndices; + _groupSettings.clear(); for (int i = 0; i < skeleton->getNumJoints(); i++) { auto name = skeleton->getJointName(i); From 70cc6594c7094ab13c5d2d0f3de4c832ecc5104a Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 8 Mar 2019 12:43:43 -0800 Subject: [PATCH 078/132] CR fixes --- .../resources/qml/hifi/commerce/purchases/PurchasedItem.qml | 2 +- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 8a20a55141..a7b36eae36 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -422,7 +422,7 @@ Item { verticalAlignment: Text.AlignVCenter; onLinkActivated: { - if(link == "#standaloneIncompatible") { + if (link === "#standaloneIncompatible") { sendToPurchases({method: 'showStandaloneIncompatibleExplanation'}); } else { sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType}); diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 4b285e5402..311c20d120 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -114,7 +114,6 @@ Rectangle { Component.onCompleted: { isStandalone = PlatformInfo.isStandalone(); - console.log(isStandalone ? "IS STANDALONE" : "ISN'T STANDALONE"); } HifiCommerceCommon.CommerceLightbox { From 1f0a8b18c8217620437f124a20938706d5751276 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 8 Mar 2019 13:01:56 -0800 Subject: [PATCH 079/132] Fix warning about TGAOrientation --- libraries/image/src/image/TGAReader.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index a6dd7e9c71..231155320a 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -26,10 +26,6 @@ QImage image::readTGA(QIODevice& content) { RunLengthEncodedTrueColor = 10, RunLengthEncodedBlackWhite = 11, }; - enum class TGAOrientation : uint8_t { - BottomLeft = 0, - TopLeft = 1, - }; struct TGAHeader { uint8_t idLength; @@ -48,13 +44,15 @@ QImage image::readTGA(QIODevice& content) { struct { uint8_t attributeBitsPerPixel : 4; uint8_t reserved : 1; - TGAOrientation orientation : 1; + uint8_t orientation : 1; uint8_t padding : 2; } imageDescriptor; }; constexpr bool WANT_DEBUG { false }; constexpr size_t TGA_HEADER_SIZE_BYTES { 18 }; + constexpr uint8_t ORIENTATION_BOTTOM_LEFT { 0 }; + constexpr uint8_t ORIENTATION_TOP_LEFT { 1 }; TGAHeader header; @@ -119,7 +117,7 @@ QImage image::readTGA(QIODevice& content) { QImage image{ header.width, header.height, QImage::Format_ARGB32 }; char* line; for (int y = 0; y < header.height; ++y) { - if (header.imageDescriptor.orientation == TGAOrientation::BottomLeft) { + if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) { line = (char*)image.scanLine(header.height - y - 1); } else { line = (char*)image.scanLine(y); @@ -137,7 +135,7 @@ QImage image::readTGA(QIODevice& content) { for (int y = 0; y < header.height; ++y) { char* line; - if (header.imageDescriptor.orientation == TGAOrientation::BottomLeft) { + if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) { line = (char*)image.scanLine(header.height - y - 1); } else { line = (char*)image.scanLine(y); From 4858f648101d905d41a38c9a6195ba693d70de65 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 14:36:47 -0700 Subject: [PATCH 080/132] get the colliding joints --- interface/src/avatar/MyAvatar.cpp | 26 ++++++++++++++++++++++++++ interface/src/avatar/MyAvatar.h | 7 +++++++ libraries/animation/src/Flow.h | 1 + 3 files changed, 34 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7e0ee5c2fd..389263656d 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5404,7 +5404,18 @@ QVariantMap MyAvatar::getFlowData() { QVariantMap physicsData; QVariantMap collisionsData; QVariantMap threadData; + std::map groupJointsMap; + QVariantList jointCollisionData; auto &groups = flow.getGroupSettings(); + for (auto &joint : flow.getJoints()) { + auto &groupName = joint.second.getGroup(); + if (groups.find(groupName) != groups.end()) { + if (groupJointsMap.find(groupName) == groupJointsMap.end()) { + groupJointsMap.insert(std::pair(groupName, QVariantList())); + } + groupJointsMap[groupName].push_back(joint.second.getIndex()); + } + } for (auto &group : groups) { QVariantMap settingsObject; QString groupName = group.first; @@ -5416,8 +5427,10 @@ QVariantMap MyAvatar::getFlowData() { settingsObject.insert("inertia", groupSettings._inertia); settingsObject.insert("radius", groupSettings._radius); settingsObject.insert("stiffness", groupSettings._stiffness); + settingsObject.insert("jointIndices", groupJointsMap[groupName]); physicsData.insert(groupName, settingsObject); } + auto &collisions = collisionSystem.getCollisions(); for (auto &collision : collisions) { QVariantMap collisionObject; @@ -5441,6 +5454,19 @@ QVariantMap MyAvatar::getFlowData() { return flowData; } +QVariantList MyAvatar::getCollidingFlowJoints() { + QVariantList collidingFlowJoints; + if (_skeletonModel->isLoaded()) { + auto& flow = _skeletonModel->getRig().getFlow(); + for (auto &joint : flow.getJoints()) { + if (joint.second.isColliding()) { + collidingFlowJoints.append(joint.second.getIndex()); + } + } + } + return collidingFlowJoints; +} + void MyAvatar::initFlowFromFST() { if (_skeletonModel->isLoaded()) { auto &flowData = _skeletonModel->getHFMModel().flowData; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c67cabe4f5..e516364f61 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1204,6 +1204,13 @@ public: */ Q_INVOKABLE QVariantMap getFlowData(); + /**jsdoc + * returns the indices of every colliding flow joint + * @function MyAvatar.getCollidingFlowJoints + * @returns {int[]} + */ + Q_INVOKABLE QVariantList getCollidingFlowJoints(); + public slots: /**jsdoc diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index d2eaaa22d9..ad81c2be77 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -222,6 +222,7 @@ public: const glm::quat& getCurrentRotation() const { return _currentRotation; } const glm::vec3& getCurrentTranslation() const { return _initialTranslation; } const glm::vec3& getInitialPosition() const { return _initialPosition; } + bool isColliding() const { return _colliding; } protected: From 24b08bd25df8a823bcfb685bc0d305da6c1eede5 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 8 Mar 2019 13:59:16 -0800 Subject: [PATCH 081/132] Change "about" blockchain from Elements to EOS --- interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index d26bf81e57..213540b334 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -97,10 +97,11 @@ Rectangle { textFormat: Text.StyledText linkColor: "#00B4EF" color: "white" - text: "Blockchain technology from Elements." + property string link: "https://eos.io/" + text: "Blockchain technology from EOS." size: 14 onLinkActivated: { - HiFiAbout.openUrl("https://elementsproject.org/elements/"); + HiFiAbout.openUrl(link); } } RalewayRegular { From 62f17acaf78cea83dbb1764ed6c36a3b9db8d74c Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 8 Mar 2019 14:01:29 -0800 Subject: [PATCH 082/132] Fix warnings in TGAReader --- libraries/image/src/image/TGAReader.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/image/src/image/TGAReader.cpp b/libraries/image/src/image/TGAReader.cpp index 231155320a..897d565eba 100644 --- a/libraries/image/src/image/TGAReader.cpp +++ b/libraries/image/src/image/TGAReader.cpp @@ -50,9 +50,10 @@ QImage image::readTGA(QIODevice& content) { }; constexpr bool WANT_DEBUG { false }; - constexpr size_t TGA_HEADER_SIZE_BYTES { 18 }; + constexpr qint64 TGA_HEADER_SIZE_BYTES { 18 }; + + // BottomLeft: 0, TopLeft: 1 constexpr uint8_t ORIENTATION_BOTTOM_LEFT { 0 }; - constexpr uint8_t ORIENTATION_TOP_LEFT { 1 }; TGAHeader header; From 88a4b5c983409fd5d8a14c1e0e6aea0d3279d7dd Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 8 Mar 2019 14:01:47 -0800 Subject: [PATCH 083/132] Case 21642 - verification dialog when user hits return in goto search text entry+return should throw up verification dialog for first time to non-optimized domain --- interface/resources/qml/hifi/Feed.qml | 9 +++++++++ .../resources/qml/hifi/tablet/TabletAddressDialog.qml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 68aab2fdd2..7f48376dc7 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -189,4 +189,13 @@ Column { } } } + function isStandalone(address) { + + for (var i=0; i < suggestions.count; i++) { + if (suggestions.get(i).place_name.toLowerCase() === address.toLowerCase()) { + return suggestions.get(i).standalone_optimized; + } + } + return false; + } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 311d20955b..4edae017d1 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -230,7 +230,7 @@ StackView { updateLocationText(text.length > 0); } onAccepted: { - toggleOrGo(); + toggleOrGo(addressLine.text, false, places.isStandalone(addressLine.text)); } // unfortunately TextField from Quick Controls 2 disallow customization of placeHolderText color without creation of new style From 06e5927ee130640f477921807c3b3074f3694842 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 8 Mar 2019 23:07:01 +0100 Subject: [PATCH 084/132] CR fixes --- interface/src/avatar/AvatarDoctor.cpp | 132 +++++++++++++------------- 1 file changed, 64 insertions(+), 68 deletions(-) diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index 8c22780b52..04a426c3db 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -55,7 +55,7 @@ static QStringList HAND_MAPPING_SUFFIXES = { "HandThumb1", }; -const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); +const QUrl DEFAULT_DOCS_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); AvatarDoctor::AvatarDoctor(const QUrl& avatarFSTFileUrl) : _avatarFSTFileUrl(avatarFSTFileUrl) { @@ -85,7 +85,7 @@ void AvatarDoctor::startDiagnosing() { const auto resourceLoaded = [this, resource](bool success) { // MODEL if (!success) { - _errors.push_back({ "Model file cannot be opened.", DEFAULT_URL }); + _errors.push_back({ "Model file cannot be opened.", DEFAULT_DOCS_URL }); emit complete(getErrors()); return; } @@ -93,49 +93,49 @@ void AvatarDoctor::startDiagnosing() { const auto model = resource.data(); const auto avatarModel = resource.data()->getHFMModel(); if (!avatarModel.originalURL.endsWith(".fbx")) { - _errors.push_back({ "Unsupported avatar model format.", DEFAULT_URL }); + _errors.push_back({ "Unsupported avatar model format.", DEFAULT_DOCS_URL }); emit complete(getErrors()); return; } // RIG if (avatarModel.joints.isEmpty()) { - _errors.push_back({ "Avatar has no rig.", DEFAULT_URL }); + _errors.push_back({ "Avatar has no rig.", DEFAULT_DOCS_URL }); } else { auto jointNames = avatarModel.getJointNames(); if (avatarModel.joints.length() > NETWORKED_JOINTS_LIMIT) { - _errors.push_back({tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), DEFAULT_URL }); + _errors.push_back({tr( "Avatar has over %n bones.", "", NETWORKED_JOINTS_LIMIT), DEFAULT_DOCS_URL }); } // Avatar does not have Hips bone mapped if (!jointNames.contains("Hips")) { - _errors.push_back({ "Hips are not mapped.", DEFAULT_URL }); + _errors.push_back({ "Hips are not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("Spine")) { - _errors.push_back({ "Spine is not mapped.", DEFAULT_URL }); + _errors.push_back({ "Spine is not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("Spine1")) { - _errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_URL }); + _errors.push_back({ "Chest (Spine1) is not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("Neck")) { - _errors.push_back({ "Neck is not mapped.", DEFAULT_URL }); + _errors.push_back({ "Neck is not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("Head")) { - _errors.push_back({ "Head is not mapped.", DEFAULT_URL }); + _errors.push_back({ "Head is not mapped.", DEFAULT_DOCS_URL }); } if (!jointNames.contains("LeftEye")) { if (jointNames.contains("RightEye")) { - _errors.push_back({ "LeftEye is not mapped.", DEFAULT_URL }); + _errors.push_back({ "LeftEye is not mapped.", DEFAULT_DOCS_URL }); } else { - _errors.push_back({ "Eyes are not mapped.", DEFAULT_URL }); + _errors.push_back({ "Eyes are not mapped.", DEFAULT_DOCS_URL }); } } else if (!jointNames.contains("RightEye")) { - _errors.push_back({ "RightEye is not mapped.", DEFAULT_URL }); + _errors.push_back({ "RightEye is not mapped.", DEFAULT_DOCS_URL }); } const auto checkJointAsymmetry = [jointNames] (const QStringList& jointMappingSuffixes) { - foreach (const QString& jointSuffix, jointMappingSuffixes) { + for (const QString& jointSuffix : jointMappingSuffixes) { if (jointNames.contains(LEFT_JOINT_PREFIX + jointSuffix) != jointNames.contains(RIGHT_JOINT_PREFIX + jointSuffix)) { return true; @@ -159,30 +159,30 @@ void AvatarDoctor::startDiagnosing() { }; if (checkJointAsymmetry(ARM_MAPPING_SUFFIXES)) { - _errors.push_back({ "Asymmetrical arm bones.", DEFAULT_URL }); + _errors.push_back({ "Asymmetrical arm bones.", DEFAULT_DOCS_URL }); } if (checkJointAsymmetry(HAND_MAPPING_SUFFIXES)) { - _errors.push_back({ "Asymmetrical hand bones.", DEFAULT_URL }); + _errors.push_back({ "Asymmetrical hand bones.", DEFAULT_DOCS_URL }); } if (checkJointAsymmetry(LEG_MAPPING_SUFFIXES)) { - _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_URL }); + _errors.push_back({ "Asymmetrical leg bones.", DEFAULT_DOCS_URL }); } // Multiple skeleton root joints checkup int skeletonRootJoints = 0; - foreach(const HFMJoint& joint, avatarModel.joints) { + for (const HFMJoint& joint: avatarModel.joints) { if (joint.parentIndex == -1 && joint.isSkeletonJoint) { skeletonRootJoints++; } } if (skeletonRootJoints > 1) { - _errors.push_back({ "Multiple root joints found.", DEFAULT_URL }); + _errors.push_back({ "Multiple top-level joints found.", DEFAULT_DOCS_URL }); } - const auto rig = new Rig(); - rig->reset(avatarModel); - const float eyeHeight = rig->getUnscaledEyeHeight(); + Rig rig; + rig.reset(avatarModel); + const float eyeHeight = rig.getUnscaledEyeHeight(); const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; const float avatarHeight = eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; @@ -191,71 +191,70 @@ void AvatarDoctor::startDiagnosing() { const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { - _errors.push_back({ "Avatar is possibly too short.", DEFAULT_URL }); + _errors.push_back({ "Avatar is possibly too short.", DEFAULT_DOCS_URL }); } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { - _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_URL }); + _errors.push_back({ "Avatar is possibly too tall.", DEFAULT_DOCS_URL }); } // HipsNotOnGround - auto hipsIndex = rig->indexOfJoint("Hips"); + auto hipsIndex = rig.indexOfJoint("Hips"); if (hipsIndex >= 0) { glm::vec3 hipsPosition; - if (rig->getJointPosition(hipsIndex, hipsPosition)) { + if (rig.getJointPosition(hipsIndex, hipsPosition)) { const auto hipJoint = avatarModel.joints.at(avatarModel.getJointIndex("Hips")); if (hipsPosition.y < HIPS_GROUND_MIN_Y) { - _errors.push_back({ "Hips are on ground.", DEFAULT_URL }); + _errors.push_back({ "Hips are on ground.", DEFAULT_DOCS_URL }); } } } // HipsSpineChestNotCoincident - auto spineIndex = rig->indexOfJoint("Spine"); - auto chestIndex = rig->indexOfJoint("Spine1"); + auto spineIndex = rig.indexOfJoint("Spine"); + auto chestIndex = rig.indexOfJoint("Spine1"); if (hipsIndex >= 0 && spineIndex >= 0 && chestIndex >= 0) { glm::vec3 hipsPosition; glm::vec3 spinePosition; glm::vec3 chestPosition; - if (rig->getJointPosition(hipsIndex, hipsPosition) && - rig->getJointPosition(spineIndex, spinePosition) && - rig->getJointPosition(chestIndex, chestPosition)) { + if (rig.getJointPosition(hipsIndex, hipsPosition) && + rig.getJointPosition(spineIndex, spinePosition) && + rig.getJointPosition(chestIndex, chestPosition)) { const auto hipsToSpine = glm::length(hipsPosition - spinePosition); const auto spineToChest = glm::length(spinePosition - chestPosition); if (hipsToSpine < HIPS_SPINE_CHEST_MIN_SEPARATION && spineToChest < HIPS_SPINE_CHEST_MIN_SEPARATION) { - _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_URL }); + _errors.push_back({ "Hips/Spine/Chest overlap.", DEFAULT_DOCS_URL }); } } } - rig->deleteLater(); auto mapping = resource->getMapping(); if (mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { const auto& jointNameMappings = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); QStringList jointValues; - foreach(const auto& jointVariant, jointNameMappings.values()) { + for (const auto& jointVariant: jointNameMappings.values()) { jointValues << jointVariant.toString(); } const auto& uniqueJointValues = jointValues.toSet(); - foreach (const auto& jointName, uniqueJointValues) { + for (const auto& jointName: uniqueJointValues) { if (jointValues.count(jointName) > 1) { - _errors.push_back({ tr("%1 is mapped multiple times.").arg(jointName), DEFAULT_URL }); + _errors.push_back({ tr("%1 is mapped multiple times.").arg(jointName), DEFAULT_DOCS_URL }); } } } if (!isDescendantOfJointWhenJointsExist("Spine", "Hips")) { - _errors.push_back({ "Spine is not a child of Hips.", DEFAULT_URL }); + _errors.push_back({ "Spine is not a child of Hips.", DEFAULT_DOCS_URL }); } if (!isDescendantOfJointWhenJointsExist("Spine1", "Spine")) { - _errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_URL }); + _errors.push_back({ "Spine1 is not a child of Spine.", DEFAULT_DOCS_URL }); } if (!isDescendantOfJointWhenJointsExist("Head", "Spine1")) { - _errors.push_back({ "Head is not a child of Spine1.", DEFAULT_URL }); + _errors.push_back({ "Head is not a child of Spine1.", DEFAULT_DOCS_URL }); } } @@ -270,7 +269,7 @@ void AvatarDoctor::startDiagnosing() { _materialMappingCount = (int)model->getMaterialMapping().size(); _materialMappingLoadedCount = 0; - foreach(const auto& materialMapping, model->getMaterialMapping()) { + for (const auto& materialMapping : model->getMaterialMapping()) { // refresh the texture mappings auto materialMappingResource = materialMapping.second; if (materialMappingResource) { @@ -301,7 +300,7 @@ void AvatarDoctor::startDiagnosing() { connect(resource.data(), &GeometryResource::finished, this, resourceLoaded); } } else { - _errors.push_back({ "Model file cannot be opened", DEFAULT_URL }); + _errors.push_back({ "Model file cannot be opened", DEFAULT_DOCS_URL }); emit complete(getErrors()); } } @@ -309,8 +308,8 @@ void AvatarDoctor::startDiagnosing() { void AvatarDoctor::diagnoseTextures() { const auto model = _model.data(); const auto avatarModel = _model.data()->getHFMModel(); - QStringList externalTextures{}; - QSet textureNames{}; + QVector externalTextures{}; + QVector textureNames{}; int texturesFound = 0; auto addTextureToList = [&externalTextures, &texturesFound](hfm::Texture texture) mutable { if (texture.filename.isEmpty()) { @@ -322,7 +321,7 @@ void AvatarDoctor::diagnoseTextures() { texturesFound++; }; - foreach(const HFMMaterial material, avatarModel.materials) { + for (const auto& material : avatarModel.materials) { addTextureToList(material.normalTexture); addTextureToList(material.albedoTexture); addTextureToList(material.opacityTexture); @@ -336,8 +335,8 @@ void AvatarDoctor::diagnoseTextures() { addTextureToList(material.lightmapTexture); } - foreach(const auto& materialMapping, model->getMaterialMapping()) { - foreach(const auto& networkMaterial, materialMapping.second.data()->parsedMaterials.networkMaterials) { + for (const auto& materialMapping : model->getMaterialMapping()) { + for (const auto& networkMaterial : materialMapping.second.data()->parsedMaterials.networkMaterials) { texturesFound += (int)networkMaterial.second->getTextureMaps().size(); } } @@ -346,39 +345,36 @@ void AvatarDoctor::diagnoseTextures() { QUrl(avatarModel.originalURL)).resolved(QUrl("textures")); if (texturesFound == 0) { - _errors.push_back({ tr("No textures assigned."), DEFAULT_URL }); + _errors.push_back({ tr("No textures assigned."), DEFAULT_DOCS_URL }); } if (!externalTextures.empty()) { // Check External Textures: auto modelTexturesURLs = model->getTextures(); _externalTextureCount = externalTextures.length(); - foreach(const QString textureKey, externalTextures) { + + auto checkTextureLoadingComplete = [this]() mutable { + if (_checkedTextureCount == _externalTextureCount) { + if (_missingTextureCount > 0) { + _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_DOCS_URL }); + } + if (_unsupportedTextureCount > 0) { + _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), + DEFAULT_DOCS_URL }); + } + + emit complete(getErrors()); + } + }; + + for (const QString& textureKey : externalTextures) { if (!modelTexturesURLs.contains(textureKey)) { _missingTextureCount++; _checkedTextureCount++; continue; } - const QUrl textureURL = modelTexturesURLs[textureKey].toUrl(); - auto textureResource = DependencyManager::get()->getTexture(textureURL); - auto checkTextureLoadingComplete = [this]() mutable { - qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount; - - if (_checkedTextureCount == _externalTextureCount) { - if (_missingTextureCount > 0) { - _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL }); - } - if (_unsupportedTextureCount > 0) { - _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), - DEFAULT_URL }); - } - - emit complete(getErrors()); - } - }; - auto textureLoaded = [this, textureResource, checkTextureLoadingComplete](bool success) mutable { if (!success) { auto normalizedURL = DependencyManager::get()->normalizeURL(textureResource->getURL()); @@ -407,9 +403,9 @@ void AvatarDoctor::diagnoseTextures() { } else { _missingTextureCount++; _checkedTextureCount++; - checkTextureLoadingComplete(); } } + checkTextureLoadingComplete(); } else { emit complete(getErrors()); } From 63c0df99458dcc3fc4cb4fd974cd7185fdb97c94 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 8 Mar 2019 03:13:56 +0100 Subject: [PATCH 085/132] temporary de-optimize game objects during the export process --- .../Assets/Editor/AvatarExporter.cs | 20 ++++++++++++++++-- tools/unity-avatar-exporter/Assets/README.txt | 2 +- .../avatarExporter.unitypackage | Bin 15811 -> 16045 bytes 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 2565a537c8..c25a962824 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -14,7 +14,7 @@ using System.Collections.Generic; class AvatarExporter : MonoBehaviour { // update version number for every PR that changes this file, also set updated version in README file - static readonly string AVATAR_EXPORTER_VERSION = "0.3.2"; + static readonly string AVATAR_EXPORTER_VERSION = "0.3.3"; static readonly float HIPS_GROUND_MIN_Y = 0.01f; static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f; @@ -364,7 +364,14 @@ class AvatarExporter : MonoBehaviour { " the Rig section of it's Inspector window.", "Ok"); return; } - + + // if the rig is optimized we should de-optimize it during the export process + bool shouldDeoptimizeGameObjects = modelImporter.optimizeGameObjects; + if (shouldDeoptimizeGameObjects) { + modelImporter.optimizeGameObjects = false; + modelImporter.SaveAndReimport(); + } + humanDescription = modelImporter.humanDescription; string textureWarnings = SetTextureDependencies(); SetBoneAndMaterialInformation(); @@ -375,6 +382,15 @@ class AvatarExporter : MonoBehaviour { // format resulting avatar rule failure strings // consider export-blocking avatar rules to be errors and show them in an error dialog, // and also include any other avatar rule failures plus texture warnings as warnings in the dialog + if (shouldDeoptimizeGameObjects) { + // switch back to optimized game object in case it was originally optimized + modelImporter.optimizeGameObjects = true; + modelImporter.SaveAndReimport(); + } + + // format resulting bone rule failure strings + // consider export-blocking bone rules to be errors and show them in an error dialog, + // and also include any other bone rule failures plus texture warnings as warnings in the dialog string boneErrors = ""; string warnings = ""; foreach (var failedAvatarRule in failedAvatarRules) { diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt index 19c31a597c..402719b497 100644 --- a/tools/unity-avatar-exporter/Assets/README.txt +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -1,6 +1,6 @@ High Fidelity, Inc. Avatar Exporter -Version 0.3.2 +Version 0.3.3 Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index c60a156711329e88a09234efca428eb3ceb6e89d..f7385e38311e93788a17308f7cb2a174c7f7f138 100644 GIT binary patch literal 16045 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%0DF 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 N Date: Fri, 8 Mar 2019 14:27:56 -0800 Subject: [PATCH 086/132] do not scroll inventory items underneath modal dialog --- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index dc892e6640..a315e08398 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -527,6 +527,7 @@ Rectangle { ListView { id: purchasesContentsList; visible: purchasesModel.count !== 0; + interactive: !lightboxPopup.visible; clip: true; model: purchasesModel; snapMode: ListView.NoSnap; From b3f5d76d0f078e49f988faf06f19e305e2c4ecd1 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 8 Mar 2019 14:37:15 -0800 Subject: [PATCH 087/132] CR Fix --- interface/resources/qml/hifi/Feed.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 7f48376dc7..718ebc9331 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -190,9 +190,10 @@ Column { } } function isStandalone(address) { - + var lowerAddress = address.toLowerCase(); + for (var i=0; i < suggestions.count; i++) { - if (suggestions.get(i).place_name.toLowerCase() === address.toLowerCase()) { + if (suggestions.get(i).place_name.toLowerCase() === lowerAddress) { return suggestions.get(i).standalone_optimized; } } From bfcb1a8391e18b8c26efb602c6ad84ed354f4690 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 15:57:25 -0700 Subject: [PATCH 088/132] Make methods thread safe --- interface/src/avatar/MyAvatar.cpp | 42 +++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 389263656d..73751f4dbe 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5333,6 +5333,15 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) } void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { + QVariantList result; + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "useFlow", + Q_ARG(bool, isActive), + Q_ARG(bool, isCollidable), + Q_ARG(const QVariantMap&, physicsConfig), + Q_ARG(const QVariantMap&, collisionsConfig)); + return; + } if (_skeletonModel->isLoaded()) { auto &flow = _skeletonModel->getRig().getFlow(); auto &collisionSystem = flow.getCollisionSystem(); @@ -5392,15 +5401,20 @@ void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& phys } QVariantMap MyAvatar::getFlowData() { - QVariantMap flowData; + QVariantMap result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "getFlowData", + Q_RETURN_ARG(QVariantMap, result)); + return result; + } if (_skeletonModel->isLoaded()) { auto jointNames = getJointNames(); auto &flow = _skeletonModel->getRig().getFlow(); auto &collisionSystem = flow.getCollisionSystem(); bool initialized = flow.isInitialized(); - flowData.insert("initialized", initialized); - flowData.insert("active", flow.getActive()); - flowData.insert("colliding", collisionSystem.getActive()); + result.insert("initialized", initialized); + result.insert("active", flow.getActive()); + result.insert("colliding", collisionSystem.getActive()); QVariantMap physicsData; QVariantMap collisionsData; QVariantMap threadData; @@ -5447,24 +5461,30 @@ QVariantMap MyAvatar::getFlowData() { } threadData.insert(thread._jointsPointer->at(thread._joints[0]).getName(), indices); } - flowData.insert("physics", physicsData); - flowData.insert("collisions", collisionsData); - flowData.insert("threads", threadData); + result.insert("physics", physicsData); + result.insert("collisions", collisionsData); + result.insert("threads", threadData); } - return flowData; + return result; } QVariantList MyAvatar::getCollidingFlowJoints() { - QVariantList collidingFlowJoints; + QVariantList result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "getCollidingFlowJoints", + Q_RETURN_ARG(QVariantList, result)); + return result; + } + if (_skeletonModel->isLoaded()) { auto& flow = _skeletonModel->getRig().getFlow(); for (auto &joint : flow.getJoints()) { if (joint.second.isColliding()) { - collidingFlowJoints.append(joint.second.getIndex()); + result.append(joint.second.getIndex()); } } } - return collidingFlowJoints; + return result; } void MyAvatar::initFlowFromFST() { From df32b61eaf08cfc6d99a2813704db014b6caba7f Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 16:01:18 -0700 Subject: [PATCH 089/132] remove unused variable --- interface/src/avatar/MyAvatar.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 73751f4dbe..9211be3b4f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -5333,7 +5333,6 @@ void MyAvatar::addAvatarHandsToFlow(const std::shared_ptr& otherAvatar) } void MyAvatar::useFlow(bool isActive, bool isCollidable, const QVariantMap& physicsConfig, const QVariantMap& collisionsConfig) { - QVariantList result; if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "useFlow", Q_ARG(bool, isActive), From 80150565f69472d4b54e2a938145f2f132a8f865 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 8 Mar 2019 16:30:28 -0700 Subject: [PATCH 090/132] Fix bug on group settings --- libraries/animation/src/Flow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 6e210fe71c..5bc2021e5e 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -786,7 +786,7 @@ Flow& Flow::operator=(const Flow& otherFlow) { } void Flow::updateGroupSettings(const QString& group, const FlowPhysicsSettings& settings) { - if (_groupSettings.find(group) != _groupSettings.end()) { + if (_groupSettings.find(group) == _groupSettings.end()) { _groupSettings.insert(std::pair(group, settings)); } else { _groupSettings[group] = settings; From 15d49fd9a923ff3ddde21b15d2f1b9fe2e2ece65 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 091/132] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++ interface/resources/qml/hifi/audio/MicBar.qml | 9 +- interface/src/Application.cpp | 18 ++ interface/src/Application.h | 2 + interface/src/scripting/Audio.cpp | 174 +++++++++++++++++- interface/src/scripting/Audio.h | 89 ++++++++- scripts/system/audio.js | 3 + 7 files changed, 302 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index c8dd83cd62..45358f59a2 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -148,6 +148,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 39f75a9182..f91058bc3c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -11,10 +11,13 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 +import stylesUit 1.0 import TabletScriptingInterface 1.0 Rectangle { + HifiConstants { id: hifi; } + readonly property var level: AudioScriptingInterface.inputLevel; property bool gated: false; @@ -131,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.muted; + visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -152,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.muted ? "MUTED" : "MUTE"; + text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6d9a1823a1..dad86e748e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1435,6 +1435,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + connect(this, &Application::pushedToTalk, + reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -4205,6 +4207,10 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; + case Qt::Key_T: + emit pushedToTalk(true); + break; + case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4310,6 +4316,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } + + switch (event->key()) { + case Qt::Key_T: + emit pushedToTalk(false); + break; + } } void Application::focusOutEvent(QFocusEvent* event) { @@ -5241,6 +5253,9 @@ void Application::loadSettings() { } } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->loadData(); + getMyAvatar()->loadData(); _settingsLoaded = true; } @@ -5250,6 +5265,9 @@ void Application::saveSettings() const { DependencyManager::get()->saveSettings(); DependencyManager::get()->saveSettings(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->saveData(); + Menu::getInstance()->saveSettings(); getMyAvatar()->saveData(); PluginManager::getInstance()->saveSettings(); diff --git a/interface/src/Application.h b/interface/src/Application.h index a8cc9450c5..1c86326f90 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,6 +358,8 @@ signals: void miniTabletEnabledChanged(bool enabled); + void pushedToTalk(bool enabled); + public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 2c4c29ff65..fe04ce47ca 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -63,26 +63,163 @@ void Audio::stopRecording() { } bool Audio::isMuted() const { - return resultWithReadLock([&] { - return _isMuted; - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getMutedHMD(); + } + else { + return getMutedDesktop(); + } } void Audio::setMuted(bool isMuted) { + withWriteLock([&] { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } + else { + setMutedDesktop(isMuted); + } + }); +} + +void Audio::setMutedDesktop(bool isMuted) { + bool changed = false; + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit desktopMutedChanged(isMuted); + } +} + +bool Audio::getMutedDesktop() const { + return resultWithReadLock([&] { + return _desktopMuted; + }); +} + +void Audio::setMutedHMD(bool isMuted) { + bool changed = false; + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + if (changed) { + emit mutedChanged(isMuted); + emit hmdMutedChanged(isMuted); + } +} + +bool Audio::getMutedHMD() const { + return resultWithReadLock([&] { + return _hmdMuted; + }); +} + +bool Audio::getPTT() { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + return getPTTHMD(); + } + else { + return getPTTDesktop(); + } +} + +bool Audio::getPushingToTalk() const { + return resultWithReadLock([&] { + return _pushingToTalk; + }); +} + +void Audio::setPTT(bool enabled) { + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setPTTHMD(enabled); + } + else { + setPTTDesktop(enabled); + } +} + +void Audio::setPTTDesktop(bool enabled) { bool changed = false; withWriteLock([&] { - if (_isMuted != isMuted) { - _isMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + if (_pttDesktop != enabled) { changed = true; + _pttDesktop = enabled; + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } } }); if (changed) { - emit mutedChanged(isMuted); + emit pushToTalkChanged(enabled); + emit pushToTalkDesktopChanged(enabled); } } +bool Audio::getPTTDesktop() const { + return resultWithReadLock([&] { + return _pttDesktop; + }); +} + +void Audio::setPTTHMD(bool enabled) { + bool changed = false; + withWriteLock([&] { + if (_pttHMD != enabled) { + changed = true; + _pttHMD = enabled; + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } + else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + } + }); + if (changed) { + emit pushToTalkChanged(enabled); + emit pushToTalkHMDChanged(enabled); + } +} + +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; @@ -179,11 +316,32 @@ void Audio::onContextChanged() { changed = true; } }); + if (isHMD) { + setMuted(getMutedHMD()); + } + else { + setMuted(getMutedDesktop()); + } if (changed) { emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); } } +void Audio::handlePushedToTalk(bool enabled) { + if (getPTT()) { + if (enabled) { + setMuted(false); + } + else { + setMuted(true); + } + if (_pushingToTalk != enabled) { + _pushingToTalk = enabled; + emit pushingToTalkChanged(enabled); + } + } +} + void Audio::setReverb(bool enable) { withWriteLock([&] { DependencyManager::get()->setReverb(enable); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index e4dcba9130..6aa589e399 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -12,6 +12,7 @@ #ifndef hifi_scripting_Audio_h #define hifi_scripting_Audio_h +#include #include "AudioScriptingInterface.h" #include "AudioDevices.h" #include "AudioEffectOptions.h" @@ -19,6 +20,9 @@ #include "AudioFileWav.h" #include +using MutedGetter = std::function; +using MutedSetter = std::function; + namespace scripting { class Audio : public AudioScriptingInterface, protected ReadWriteLockable { @@ -63,6 +67,12 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged) Q_PROPERTY(QString context READ getContext NOTIFY contextChanged) Q_PROPERTY(AudioDevices* devices READ getDevices NOTIFY nop) + Q_PROPERTY(bool desktopMuted READ getMutedDesktop WRITE setMutedDesktop NOTIFY desktopMutedChanged) + Q_PROPERTY(bool hmdMuted READ getMutedHMD WRITE setMutedHMD NOTIFY hmdMutedChanged) + Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); + Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) + Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; @@ -82,6 +92,25 @@ public: void showMicMeter(bool show); + // Mute setting setters and getters + void setMutedDesktop(bool isMuted); + bool getMutedDesktop() const; + void setMutedHMD(bool isMuted); + bool getMutedHMD() const; + void setPTT(bool enabled); + bool getPTT(); + bool getPushingToTalk() const; + + // Push-To-Talk setters and getters + void setPTTDesktop(bool enabled); + bool getPTTDesktop() const; + void setPTTHMD(bool enabled); + bool getPTTHMD() const; + + // Settings handlers + void saveData(); + void loadData(); + /**jsdoc * @function Audio.setInputDevice * @param {object} device @@ -193,6 +222,46 @@ signals: */ void mutedChanged(bool isMuted); + /**jsdoc + * Triggered when desktop audio input is muted or unmuted. + * @function Audio.desktopMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. + * @returns {Signal} + */ + void desktopMutedChanged(bool isMuted); + + /**jsdoc + * Triggered when HMD audio input is muted or unmuted. + * @function Audio.hmdMutedChanged + * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. + * @returns {Signal} + */ + void hmdMutedChanged(bool isMuted); + + /** + * Triggered when Push-to-Talk has been enabled or disabled. + * @function Audio.pushToTalkChanged + * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. + * @returns {Signal} + */ + void pushToTalkChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. + * @function Audio.pushToTalkDesktopChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkDesktopChanged(bool enabled); + + /** + * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. + * @function Audio.pushToTalkHMDChanged + * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. + * @returns {Signal} + */ + void pushToTalkHMDChanged(bool enabled); + /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged @@ -237,6 +306,14 @@ signals: */ void contextChanged(const QString& context); + /**jsdoc + * Triggered when pushing to talk. + * @function Audio.pushingToTalkChanged + * @param {boolean} talking - true if broadcasting with PTT, false otherwise. + * @returns {Signal} + */ + void pushingToTalkChanged(bool talking); + public slots: /**jsdoc @@ -245,6 +322,8 @@ public slots: */ void onContextChanged(); + void handlePushedToTalk(bool enabled); + private slots: void setMuted(bool muted); void enableNoiseReduction(bool enable); @@ -260,11 +339,19 @@ private: float _inputVolume { 1.0f }; float _inputLevel { 0.0f }; bool _isClipping { false }; - bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; + Setting::Handle _desktopMutedSetting{ QStringList { Audio::AUDIO, "desktopMuted" }, true }; + Setting::Handle _hmdMutedSetting{ QStringList { Audio::AUDIO, "hmdMuted" }, true }; + Setting::Handle _pttDesktopSetting{ QStringList { Audio::AUDIO, "pushToTalkDesktop" }, false }; + Setting::Handle _pttHMDSetting{ QStringList { Audio::AUDIO, "pushToTalkHMD" }, false }; + bool _desktopMuted{ true }; + bool _hmdMuted{ false }; + bool _pttDesktop{ false }; + bool _pttHMD{ false }; + bool _pushingToTalk{ false }; }; }; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index ee82c0c6ea..51d070d8cd 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -28,6 +28,9 @@ var UNMUTE_ICONS = { }; function onMuteToggled() { + if (Audio.pushingToTalk) { + return; + } if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { From 7cb17b2d6ea31da2aefb0f775569ca1abf43e637 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:00:09 -0800 Subject: [PATCH 092/132] laying groundwork for audio app + fixing deadlocks --- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- interface/src/scripting/Audio.cpp | 108 +++++++++--------- interface/src/scripting/Audio.h | 1 + scripts/system/audio.js | 11 +- 4 files changed, 69 insertions(+), 57 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index f91058bc3c..2ab1085408 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,7 +134,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: 50; + width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; height: 4; color: parent.color; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index fe04ce47ca..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -66,32 +66,30 @@ bool Audio::isMuted() const { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getMutedHMD(); - } - else { + } else { return getMutedDesktop(); } } void Audio::setMuted(bool isMuted) { - withWriteLock([&] { - bool isHMD = qApp->isHMDMode(); - if (isHMD) { - setMutedHMD(isMuted); - } - else { - setMutedDesktop(isMuted); - } - }); + bool isHMD = qApp->isHMDMode(); + if (isHMD) { + setMutedHMD(isMuted); + } else { + setMutedDesktop(isMuted); + } } void Audio::setMutedDesktop(bool isMuted) { bool changed = false; - if (_desktopMuted != isMuted) { - changed = true; - _desktopMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_desktopMuted != isMuted) { + changed = true; + _desktopMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit desktopMutedChanged(isMuted); @@ -106,12 +104,14 @@ bool Audio::getMutedDesktop() const { void Audio::setMutedHMD(bool isMuted) { bool changed = false; - if (_hmdMuted != isMuted) { - changed = true; - _hmdMuted = isMuted; - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); - } + withWriteLock([&] { + if (_hmdMuted != isMuted) { + changed = true; + _hmdMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + } + }); if (changed) { emit mutedChanged(isMuted); emit hmdMutedChanged(isMuted); @@ -128,12 +128,24 @@ bool Audio::getPTT() { bool isHMD = qApp->isHMDMode(); if (isHMD) { return getPTTHMD(); - } - else { + } else { return getPTTDesktop(); } } +void scripting::Audio::setPushingToTalk(bool pushingToTalk) { + bool changed = false; + withWriteLock([&] { + if (_pushingToTalk != pushingToTalk) { + changed = true; + _pushingToTalk = pushingToTalk; + } + }); + if (changed) { + emit pushingToTalkChanged(pushingToTalk); + } +} + bool Audio::getPushingToTalk() const { return resultWithReadLock([&] { return _pushingToTalk; @@ -144,8 +156,7 @@ void Audio::setPTT(bool enabled) { bool isHMD = qApp->isHMDMode(); if (isHMD) { setPTTHMD(enabled); - } - else { + } else { setPTTDesktop(enabled); } } @@ -156,16 +167,16 @@ void Audio::setPTTDesktop(bool enabled) { if (_pttDesktop != enabled) { changed = true; _pttDesktop = enabled; - if (!enabled) { - // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. - setMutedDesktop(true); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedDesktop(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for Desktop) on Push-To-Talk disable. + setMutedDesktop(true); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedDesktop(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkDesktopChanged(enabled); @@ -184,16 +195,16 @@ void Audio::setPTTHMD(bool enabled) { if (_pttHMD != enabled) { changed = true; _pttHMD = enabled; - if (!enabled) { - // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. - setMutedHMD(false); - } - else { - // Should be muted when not pushing to talk while PTT is enabled. - setMutedHMD(true); - } } }); + if (!enabled) { + // Set to default behavior (unmuted for HMD) on Push-To-Talk disable. + setMutedHMD(false); + } else { + // Should be muted when not pushing to talk while PTT is enabled. + setMutedHMD(true); + } + if (changed) { emit pushToTalkChanged(enabled); emit pushToTalkHMDChanged(enabled); @@ -318,8 +329,7 @@ void Audio::onContextChanged() { }); if (isHMD) { setMuted(getMutedHMD()); - } - else { + } else { setMuted(getMutedDesktop()); } if (changed) { @@ -331,14 +341,10 @@ void Audio::handlePushedToTalk(bool enabled) { if (getPTT()) { if (enabled) { setMuted(false); - } - else { + } else { setMuted(true); } - if (_pushingToTalk != enabled) { - _pushingToTalk = enabled; - emit pushingToTalkChanged(enabled); - } + setPushingToTalk(enabled); } } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 6aa589e399..94f8a7bf54 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -99,6 +99,7 @@ public: bool getMutedHMD() const; void setPTT(bool enabled); bool getPTT(); + void setPushingToTalk(bool pushingToTalk); bool getPushingToTalk() const; // Push-To-Talk setters and getters diff --git a/scripts/system/audio.js b/scripts/system/audio.js index 51d070d8cd..bf44cfa7cc 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -26,12 +26,15 @@ var UNMUTE_ICONS = { icon: "icons/tablet-icons/mic-unmute-i.svg", activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; +var PTT_ICONS = { + icon: "icons/tablet-icons/mic-unmute-i.svg", + activeIcon: "icons/tablet-icons/mic-unmute-a.svg" +}; function onMuteToggled() { if (Audio.pushingToTalk) { - return; - } - if (Audio.muted) { + button.editProperties(PTT_ICONS); + } else if (Audio.muted) { button.editProperties(MUTE_ICONS); } else { button.editProperties(UNMUTE_ICONS); @@ -71,6 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); +Audio.pushingToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -79,6 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); + Audio.pushingToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From 60b98d2c70b2b9366d9d1d6abd18e9136d566a69 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:11:47 -0800 Subject: [PATCH 093/132] Push to talk changes + rebased with master (#7) Push to talk changes + rebased with master --- interface/src/scripting/Audio.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 63ce9d2b2e..45bb15f1a3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,6 +231,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; From 8a5924077a24d0892ab402dceb0c1a258ce07860 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:18:57 -0800 Subject: [PATCH 094/132] changing text display --- interface/resources/qml/hifi/audio/MicBar.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 2ab1085408..9d1cbfbc6c 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -134,9 +134,9 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; - visible: AudioScriptingInterface.pushingToTalk || AudioScriptingInterface.muted; + visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; anchors { left: parent.left; @@ -155,7 +155,7 @@ Rectangle { color: parent.color; - text: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? "SPEAKING" : "PUSH TO TALK") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -165,7 +165,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } @@ -176,7 +176,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk ? (AudioScriptingInterface.pushingToTalk ? 45: 30) : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; height: 4; color: parent.color; } From a688c0a92b56062b46442f9e25d6d8b57f77294d Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:45:43 -0800 Subject: [PATCH 095/132] exposing setting pushingToTalk --- interface/src/scripting/Audio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 94f8a7bf54..9ad4aac9c1 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -72,7 +72,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool pushToTalk READ getPTT WRITE setPTT NOTIFY pushToTalkChanged); Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) - Q_PROPERTY(bool pushingToTalk READ getPushingToTalk NOTIFY pushingToTalkChanged) + Q_PROPERTY(bool pushingToTalk READ getPushingToTalk WRITE setPushingToTalk NOTIFY pushingToTalkChanged) public: static QString AUDIO; From b7d44403e16e76e8d7b1c672c457946e1933a378 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 17:29:32 -0800 Subject: [PATCH 096/132] updating compile failure + icons/settings update --- .../icons/tablet-icons/mic-ptt-a.svg | 1 + .../icons/tablet-icons/mic-ptt-i.svg | 24 +++++++ interface/resources/qml/hifi/audio/Audio.qml | 64 ++++++++++++++++--- interface/resources/qml/hifi/audio/MicBar.qml | 8 ++- interface/src/scripting/Audio.cpp | 20 ------ scripts/system/audio.js | 14 ++-- 6 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-a.svg create mode 100644 interface/resources/icons/tablet-icons/mic-ptt-i.svg diff --git a/interface/resources/icons/tablet-icons/mic-ptt-a.svg b/interface/resources/icons/tablet-icons/mic-ptt-a.svg new file mode 100644 index 0000000000..e6df3c69d7 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-a.svg @@ -0,0 +1 @@ +mic-ptt-a \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/mic-ptt-i.svg b/interface/resources/icons/tablet-icons/mic-ptt-i.svg new file mode 100644 index 0000000000..2141ea5229 --- /dev/null +++ b/interface/resources/icons/tablet-icons/mic-ptt-i.svg @@ -0,0 +1,24 @@ + + + + + + diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 45358f59a2..d44a9c862e 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -120,6 +120,10 @@ Rectangle { isRedCheck: true; checked: AudioScriptingInterface.muted; onClicked: { + if (AudioScriptingInterface.pushToTalk && !checked) { + // disable push to talk if unmuting + AudioScriptingInterface.pushToTalk = false; + } AudioScriptingInterface.muted = checked; checked = Qt.binding(function() { return AudioScriptingInterface.muted; }); // restore binding } @@ -150,7 +154,23 @@ Rectangle { } AudioControls.CheckBox { spacing: muteMic.spacing - text: qsTr("Push To Talk"); + text: qsTr("Show audio level meter"); + checked: AvatarInputs.showAudioTools; + onClicked: { + AvatarInputs.showAudioTools = checked; + checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + } + onXChanged: rightMostInputLevelPos = x + width + } + } + + Separator {} + + ColumnLayout { + spacing: muteMic.spacing; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { if (isVR) { @@ -167,15 +187,41 @@ Rectangle { }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); - checked: AvatarInputs.showAudioTools; - onClicked: { - AvatarInputs.showAudioTools = checked; - checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + Item { + id: pttTextContainer + x: margins.paddings; + width: rightMostInputLevelPos + height: pttTextMetrics.height + visible: true + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + wrapMode: Text.WordWrap + color: hifi.colors.white; + width: parent.width; + font.italic: true + size: 16; + text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : + qsTr("Press and hold the button \"T\" to unmute."); + onTextChanged: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } + } + } + Component.onCompleted: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } } - onXChanged: rightMostInputLevelPos = x + width } } } diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 9d1cbfbc6c..50477b82f8 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -67,6 +67,9 @@ Rectangle { hoverEnabled: true; scrollGestureEnabled: false; onClicked: { + if (AudioScriptingInterface.pushToTalk) { + return; + } AudioScriptingInterface.muted = !AudioScriptingInterface.muted; Tablet.playSound(TabletEnums.ButtonClick); } @@ -109,9 +112,10 @@ Rectangle { Image { readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg"; readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg"; + readonly property string pushToTalkIcon: "../../../icons/tablet-icons/mic-ptt-i.svg"; id: image; - source: AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; + source: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? pushToTalkIcon : AudioScriptingInterface.muted ? mutedIcon : unmutedIcon; width: 30; height: 30; @@ -155,7 +159,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED-PTT (T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED PTT-(T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 45bb15f1a3..63ce9d2b2e 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,26 +231,6 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; diff --git a/scripts/system/audio.js b/scripts/system/audio.js index bf44cfa7cc..19ed3faef2 100644 --- a/scripts/system/audio.js +++ b/scripts/system/audio.js @@ -27,12 +27,12 @@ var UNMUTE_ICONS = { activeIcon: "icons/tablet-icons/mic-unmute-a.svg" }; var PTT_ICONS = { - icon: "icons/tablet-icons/mic-unmute-i.svg", - activeIcon: "icons/tablet-icons/mic-unmute-a.svg" + icon: "icons/tablet-icons/mic-ptt-i.svg", + activeIcon: "icons/tablet-icons/mic-ptt-a.svg" }; function onMuteToggled() { - if (Audio.pushingToTalk) { + if (Audio.pushToTalk) { button.editProperties(PTT_ICONS); } else if (Audio.muted) { button.editProperties(MUTE_ICONS); @@ -63,8 +63,8 @@ function onScreenChanged(type, url) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ - icon: Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, - activeIcon: Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, + icon: Audio.pushToTalk ? PTT_ICONS.icon : Audio.muted ? MUTE_ICONS.icon : UNMUTE_ICONS.icon, + activeIcon: Audio.pushToTalk ? PTT_ICONS.activeIcon : Audio.muted ? MUTE_ICONS.activeIcon : UNMUTE_ICONS.activeIcon, text: TABLET_BUTTON_NAME, sortOrder: 1 }); @@ -74,7 +74,7 @@ onMuteToggled(); button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); Audio.mutedChanged.connect(onMuteToggled); -Audio.pushingToTalkChanged.connect(onMuteToggled); +Audio.pushToTalkChanged.connect(onMuteToggled); Script.scriptEnding.connect(function () { if (onAudioScreen) { @@ -83,7 +83,7 @@ Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.screenChanged.disconnect(onScreenChanged); Audio.mutedChanged.disconnect(onMuteToggled); - Audio.pushingToTalkChanged.disconnect(onMuteToggled); + Audio.pushToTalkChanged.disconnect(onMuteToggled); tablet.removeButton(button); }); From f6add9cafdd87c6a253c344f905c295cbaa617fc Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:28 -0800 Subject: [PATCH 097/132] Add pushToTalk.js controllerModule. --- .../controllerModules/pushToTalk.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scripts/system/controllers/controllerModules/pushToTalk.js diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js new file mode 100644 index 0000000000..e764b228c9 --- /dev/null +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -0,0 +1,75 @@ +"use strict"; + +// Created by Jason C. Najera on 3/7/2019 +// Copyright 2019 High Fidelity, Inc. +// +// Handles Push-to-Talk functionality for HMD mode. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { // BEGIN LOCAL_SCOPE + function PushToTalkHandler() { + var _this = this; + this.active = false; + //var pttMapping, mappingName; + + this.setup = function() { + //mappingName = 'Hifi-PTT-Dev-' + Math.random(); + //pttMapping = Controller.newMapping(mappingName); + //pttMapping.enable(); + }; + + this.shouldTalk = function (controllerData) { + // Set up test against controllerData here... + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? true : false; + }; + + this.shouldStopTalking = function (controllerData) { + var gripVal = controllerData.secondaryValues[this.hand]; + return (gripVal) ? false : true; + }; + + this.isReady = function (controllerData, deltaTime) { + if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + Audio.pushingToTalk = true; + returnMakeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { + Audio.pushingToTalk = false; + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + //pttMapping.disable(); + }; + + this.parameters = makeDispatcherModuleParameters( + 950, + ["head"], + [], + 100); + } + + var pushToTalk = new PushToTalkHandler(); + enableDispatcherModule("PushToTalk", pushToTalk); + + function cleanup () { + pushToTalk.cleanup(); + disableDispatcherModule("PushToTalk"); + }; + + Script.scriptEnding.connect(cleanup); +}()); // END LOCAL_SCOPE \ No newline at end of file From 3b274c2b6e7dbe77a9e89b255ae197c36e9184b8 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:31:48 -0800 Subject: [PATCH 098/132] Enable pushToTalk.js controller module. --- scripts/system/controllers/controllerDispatcher.js | 1 + scripts/system/controllers/controllerScripts.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 28c3e2a299..f4c0cbb0d6 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -58,6 +58,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // not set to false (not in use), a module cannot start. When a module is using a slot, that module's name // is stored as the value, rather than false. this.activitySlots = { + head: false, leftHand: false, rightHand: false, rightHandTrigger: false, diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 726e075fcc..ca7d041792 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -33,7 +33,8 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearGrabHyperLinkEntity.js", "controllerModules/nearTabletHighlight.js", "controllerModules/nearGrabEntity.js", - "controllerModules/farGrabEntity.js" + "controllerModules/farGrabEntity.js", + "controllerModules/pushToTalk.js" ]; var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; From 88a125aff0a5dd3de6d523e5325ddedc24dff30e Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Tue, 5 Mar 2019 17:09:54 -0800 Subject: [PATCH 099/132] Initial implementation (deadlocks still occurring in Audio.cpp) --- interface/resources/qml/hifi/audio/Audio.qml | 19 ++++++++ interface/resources/qml/hifi/audio/MicBar.qml | 18 ++++---- interface/src/scripting/Audio.cpp | 20 ++++++++ interface/src/scripting/Audio.h | 46 +++++++++---------- 4 files changed, 71 insertions(+), 32 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index d44a9c862e..9e9a8c0022 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -152,6 +152,25 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk"); + checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; + onClicked: { + if (isVR) { + AudioScriptingInterface.pushToTalkHMD = checked; + } else { + AudioScriptingInterface.pushToTalkDesktop = checked; + } + checked = Qt.binding(function() { + if (isVR) { + return AudioScriptingInterface.pushToTalkHMD; + } else { + return AudioScriptingInterface.pushToTalkDesktop; + } + }); // restore binding + } + } AudioControls.CheckBox { spacing: muteMic.spacing text: qsTr("Show audio level meter"); diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 50477b82f8..6cb45eaecb 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -19,13 +19,13 @@ Rectangle { HifiConstants { id: hifi; } readonly property var level: AudioScriptingInterface.inputLevel; - + property bool gated: false; Component.onCompleted: { AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; }); AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; }); } - + property bool standalone: false; property var dragTarget: null; @@ -138,7 +138,7 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; @@ -235,12 +235,12 @@ Rectangle { } } } - + Rectangle { id: gatedIndicator; visible: gated && !AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: "#0080FF"; @@ -249,12 +249,12 @@ Rectangle { verticalCenter: parent.verticalCenter; } } - + Rectangle { id: clippingIndicator; visible: AudioScriptingInterface.clipping - - radius: 4; + + radius: 4; width: 2 * radius; height: 2 * radius; color: colors.red; diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 63ce9d2b2e..45bb15f1a3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -231,6 +231,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 9ad4aac9c1..10aceb02fb 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -41,16 +41,16 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * @hifi-assignment-client * * @property {boolean} muted - true if the audio input is muted, otherwise false. - * @property {boolean} noiseReduction - true if noise reduction is enabled, otherwise false. When - * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just + * @property {boolean} noiseReduction - true if noise reduction is enabled, otherwise false. When + * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just * above the noise floor. - * @property {number} inputLevel - The loudness of the audio input, range 0.0 (no sound) – + * @property {number} inputLevel - The loudness of the audio input, range 0.0 (no sound) – * 1.0 (the onset of clipping). Read-only. * @property {boolean} clipping - true if the audio input is clipping, otherwise false. - * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.01.0. - * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some + * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.01.0. + * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some * devices, and others might only support values of 0.0 and 1.0. - * @property {boolean} isStereoInput - true if the input audio is being used in stereo, otherwise + * @property {boolean} isStereoInput - true if the input audio is being used in stereo, otherwise * false. Some devices do not support stereo, in which case the value is always false. * @property {string} context - The current context of the audio: either "Desktop" or "HMD". * Read-only. @@ -115,7 +115,7 @@ public: /**jsdoc * @function Audio.setInputDevice * @param {object} device - * @param {boolean} isHMD + * @param {boolean} isHMD * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD); @@ -129,8 +129,8 @@ public: Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD); /**jsdoc - * Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options - * come from either the domain's audio zone if used — configured on the server — or as scripted by + * Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options + * come from either the domain's audio zone if used — configured on the server — or as scripted by * {@link Audio.setReverbOptions|setReverbOptions}. * @function Audio.setReverb * @param {boolean} enable - true to enable reverberation, false to disable. @@ -140,13 +140,13 @@ public: * var injectorOptions = { * position: MyAvatar.position * }; - * + * * Script.setTimeout(function () { * print("Reverb OFF"); * Audio.setReverb(false); * injector = Audio.playSound(sound, injectorOptions); * }, 1000); - * + * * Script.setTimeout(function () { * var reverbOptions = new AudioEffectOptions(); * reverbOptions.roomSize = 100; @@ -154,26 +154,26 @@ public: * print("Reverb ON"); * Audio.setReverb(true); * }, 4000); - * + * * Script.setTimeout(function () { * print("Reverb OFF"); * Audio.setReverb(false); * }, 8000); */ Q_INVOKABLE void setReverb(bool enable); - + /**jsdoc * Configure reverberation options. Use {@link Audio.setReverb|setReverb} to enable or disable reverberation. * @function Audio.setReverbOptions * @param {AudioEffectOptions} options - The reverberation options. */ Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); - + /**jsdoc * Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format. * @function Audio.startRecording - * @param {string} filename - The path and name of the file to make the recording in. Should have a .wav + * @param {string} filename - The path and name of the file to make the recording in. Should have a .wav * extension. The file is overwritten if it already exists. - * @returns {boolean} true if the specified file could be opened and audio recording has started, otherwise + * @returns {boolean} true if the specified file could be opened and audio recording has started, otherwise * false. * @example Make a 10 second audio recording. * var filename = File.getTempDir() + "/audio.wav"; @@ -182,13 +182,13 @@ public: * Audio.stopRecording(); * print("Audio recording made in: " + filename); * }, 10000); - * + * * } else { * print("Could not make an audio recording in: " + filename); * } */ Q_INVOKABLE bool startRecording(const QString& filename); - + /**jsdoc * Finish making an audio recording started with {@link Audio.startRecording|startRecording}. * @function Audio.stopRecording @@ -222,7 +222,7 @@ signals: * }); */ void mutedChanged(bool isMuted); - + /**jsdoc * Triggered when desktop audio input is muted or unmuted. * @function Audio.desktopMutedChanged @@ -274,9 +274,9 @@ signals: /**jsdoc * Triggered when the input audio volume changes. * @function Audio.inputVolumeChanged - * @param {number} volume - The requested volume to be applied to the audio input, range 0.0 – - * 1.0. The resulting value of Audio.inputVolume depends on the capabilities of the device: - * for example, the volume can't be changed on some devices, and others might only support values of 0.0 + * @param {number} volume - The requested volume to be applied to the audio input, range 0.0 – + * 1.0. The resulting value of Audio.inputVolume depends on the capabilities of the device: + * for example, the volume can't be changed on some devices, and others might only support values of 0.0 * and 1.0. * @returns {Signal} */ @@ -285,7 +285,7 @@ signals: /**jsdoc * Triggered when the input audio level changes. * @function Audio.inputLevelChanged - * @param {number} level - The loudness of the input audio, range 0.0 (no sound) – 1.0 (the + * @param {number} level - The loudness of the input audio, range 0.0 (no sound) – 1.0 (the * onset of clipping). * @returns {Signal} */ From cb0fdd2ef2ff91854b0c4962bb2bd12bac4aa551 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:00:09 -0800 Subject: [PATCH 100/132] laying groundwork for audio app + fixing deadlocks --- interface/resources/qml/hifi/audio/MicBar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 6cb45eaecb..41ab0b6e91 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -138,7 +138,7 @@ Rectangle { Item { id: status; - readonly property string color: (AudioScriptingInterface.pushingToTalk && AudioScriptingInterface.muted) ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; From f4db9fefd0d81e539491492981b32a8a75a51964 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:11:47 -0800 Subject: [PATCH 101/132] Push to talk changes + rebased with master (#7) Push to talk changes + rebased with master --- interface/src/scripting/Audio.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 45bb15f1a3..0a859c4dcc 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -251,6 +251,26 @@ void Audio::loadData() { _pttHMD = _pttHMDSetting.get(); } +bool Audio::getPTTHMD() const { + return resultWithReadLock([&] { + return _pttHMD; + }); +} + +void Audio::saveData() { + _desktopMutedSetting.set(getMutedDesktop()); + _hmdMutedSetting.set(getMutedHMD()); + _pttDesktopSetting.set(getPTTDesktop()); + _pttHMDSetting.set(getPTTHMD()); +} + +void Audio::loadData() { + _desktopMuted = _desktopMutedSetting.get(); + _hmdMuted = _hmdMutedSetting.get(); + _pttDesktop = _pttDesktopSetting.get(); + _pttHMD = _pttHMDSetting.get(); +} + bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; From 39db3979b9eb8e46b71623752ff4785fcb6f93dc Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 11:18:57 -0800 Subject: [PATCH 102/132] changing text display --- interface/resources/qml/hifi/audio/MicBar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 41ab0b6e91..491b9f9554 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -138,7 +138,7 @@ Rectangle { Item { id: status; - readonly property string color: AudioScriptingInterface.pushToTalk ? hifi.colors.blueHighlight : AudioScriptingInterface.muted ? colors.muted : colors.unmuted; + readonly property string color: AudioScriptingInterface.muted ? colors.muted : colors.unmuted; visible: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) || AudioScriptingInterface.muted; From 88d8163b04e8b740c1433530a8821f94dae7ae3b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 6 Mar 2019 17:29:32 -0800 Subject: [PATCH 103/132] updating compile failure + icons/settings update --- interface/resources/qml/hifi/audio/Audio.qml | 60 +++++++++++++++++--- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 9e9a8c0022..569cd23176 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -154,7 +154,23 @@ Rectangle { } AudioControls.CheckBox { spacing: muteMic.spacing - text: qsTr("Push To Talk"); + text: qsTr("Show audio level meter"); + checked: AvatarInputs.showAudioTools; + onClicked: { + AvatarInputs.showAudioTools = checked; + checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + } + onXChanged: rightMostInputLevelPos = x + width + } + } + + Separator {} + + ColumnLayout { + spacing: muteMic.spacing; + AudioControls.CheckBox { + spacing: muteMic.spacing + text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { if (isVR) { @@ -171,15 +187,41 @@ Rectangle { }); // restore binding } } - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Show audio level meter"); - checked: AvatarInputs.showAudioTools; - onClicked: { - AvatarInputs.showAudioTools = checked; - checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding + Item { + id: pttTextContainer + x: margins.paddings; + width: rightMostInputLevelPos + height: pttTextMetrics.height + visible: true + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + wrapMode: Text.WordWrap + color: hifi.colors.white; + width: parent.width; + font.italic: true + size: 16; + text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : + qsTr("Press and hold the button \"T\" to unmute."); + onTextChanged: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } + } + } + Component.onCompleted: { + if (pttTextMetrics.width > rightMostInputLevelPos) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } else { + pttTextContainer.height = pttTextMetrics.height; + } } - onXChanged: rightMostInputLevelPos = x + width } } From 24d6646e8d23f6aab6b441a7b097e17bd41ddbb4 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 11:45:59 -0800 Subject: [PATCH 104/132] Fix activation / deactivation criteria for PTT controller module. --- scripts/system/controllers/controllerModules/pushToTalk.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index e764b228c9..557476ccd7 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -25,12 +25,12 @@ Script.include("/~/system/libraries/controllers.js"); this.shouldTalk = function (controllerData) { // Set up test against controllerData here... - var gripVal = controllerData.secondaryValues[this.hand]; + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; return (gripVal) ? true : false; }; this.shouldStopTalking = function (controllerData) { - var gripVal = controllerData.secondaryValues[this.hand]; + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; return (gripVal) ? false : true; }; From 5f48a6d1044fd11cab6110db39eca367448ab0e4 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Thu, 7 Mar 2019 12:35:04 -0800 Subject: [PATCH 105/132] Fix typo. --- scripts/system/controllers/controllerModules/pushToTalk.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index 557476ccd7..dd959ae6fb 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -35,7 +35,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData, deltaTime) { - if (HMD.active() && Audio.pushToTalk && this.shouldTalk(controllerData)) { + if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { Audio.pushingToTalk = true; returnMakeRunningValues(true, [], []); } From 18b86d550de3f71290b354e1e4ff602ba4dd03ff Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 7 Mar 2019 12:36:56 -0800 Subject: [PATCH 106/132] adding PushToTalk action --- interface/src/Application.cpp | 11 +++++++++++ libraries/controllers/src/controllers/Actions.cpp | 4 ++++ libraries/controllers/src/controllers/Actions.h | 1 + .../controllers/controllerModules/pushToTalk.js | 2 +- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dad86e748e..c0cacd4e40 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1601,12 +1601,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { using namespace controller; auto tabletScriptingInterface = DependencyManager::get(); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); { auto actionEnum = static_cast(action); int key = Qt::Key_unknown; static int lastKey = Qt::Key_unknown; bool navAxis = false; switch (actionEnum) { + case Action::TOGGLE_PUSHTOTALK: + if (audioScriptingInterface->getPTT()) { + qDebug() << "State is " << state; + if (state > 0.0f) { + audioScriptingInterface->setPushingToTalk(false); + } else if (state < 0.0f) { + audioScriptingInterface->setPushingToTalk(true); + } + } + case Action::UI_NAV_VERTICAL: navAxis = true; if (state > 0.0f) { diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 5a396231b6..57be2f788b 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -180,6 +180,7 @@ namespace controller { * third person, to full screen mirror, then back to first person and repeat. * ContextMenunumbernumberShow / hide the tablet. * ToggleMutenumbernumberToggle the microphone mute. + * TogglePushToTalknumbernumberToggle push to talk. * ToggleOverlaynumbernumberToggle the display of overlays. * SprintnumbernumberSet avatar sprint mode. * ReticleClicknumbernumberSet mouse-pressed. @@ -245,6 +246,8 @@ namespace controller { * ContextMenu instead. * TOGGLE_MUTEnumbernumberDeprecated: Use * ToggleMute instead. + * TOGGLE_PUSHTOTALKnumbernumberDeprecated: Use + * TogglePushToTalk instead. * SPRINTnumbernumberDeprecated: Use * Sprint instead. * LONGITUDINAL_BACKWARDnumbernumberDeprecated: Use @@ -411,6 +414,7 @@ namespace controller { makeButtonPair(Action::ACTION2, "SecondaryAction"), makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"), makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"), + makeButtonPair(Action::TOGGLE_PUSHTOTALK, "TogglePushToTalk"), makeButtonPair(Action::CYCLE_CAMERA, "CycleCamera"), makeButtonPair(Action::TOGGLE_OVERLAY, "ToggleOverlay"), makeButtonPair(Action::SPRINT, "Sprint"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index a12a3d60a9..3e99d8d147 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -60,6 +60,7 @@ enum class Action { CONTEXT_MENU, TOGGLE_MUTE, + TOGGLE_PUSHTOTALK, CYCLE_CAMERA, TOGGLE_OVERLAY, diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index dd959ae6fb..9d6435f497 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -72,4 +72,4 @@ Script.include("/~/system/libraries/controllers.js"); }; Script.scriptEnding.connect(cleanup); -}()); // END LOCAL_SCOPE \ No newline at end of file +}()); // END LOCAL_SCOPE From 48d1ec850c64e16822ce495cf55d0f1c8a9b60cf Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Thu, 7 Mar 2019 12:54:32 -0800 Subject: [PATCH 107/132] removing debug statement --- interface/src/Application.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c0cacd4e40..fc9fcd1bbb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1610,7 +1610,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo switch (actionEnum) { case Action::TOGGLE_PUSHTOTALK: if (audioScriptingInterface->getPTT()) { - qDebug() << "State is " << state; if (state > 0.0f) { audioScriptingInterface->setPushingToTalk(false); } else if (state < 0.0f) { From e0fe11056e8e49e529eddc12930b50f737d55966 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Fri, 8 Mar 2019 16:37:41 -0800 Subject: [PATCH 108/132] fixing compile error --- interface/src/scripting/Audio.h | 40 --------------------------------- 1 file changed, 40 deletions(-) diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index ce857211e0..10aceb02fb 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -263,46 +263,6 @@ signals: */ void pushToTalkHMDChanged(bool enabled); - /**jsdoc - * Triggered when desktop audio input is muted or unmuted. - * @function Audio.desktopMutedChanged - * @param {boolean} isMuted - true if the audio input is muted for desktop mode, otherwise false. - * @returns {Signal} - */ - void desktopMutedChanged(bool isMuted); - - /**jsdoc - * Triggered when HMD audio input is muted or unmuted. - * @function Audio.hmdMutedChanged - * @param {boolean} isMuted - true if the audio input is muted for HMD mode, otherwise false. - * @returns {Signal} - */ - void hmdMutedChanged(bool isMuted); - - /** - * Triggered when Push-to-Talk has been enabled or disabled. - * @function Audio.pushToTalkChanged - * @param {boolean} enabled - true if Push-to-Talk is enabled, otherwise false. - * @returns {Signal} - */ - void pushToTalkChanged(bool enabled); - - /** - * Triggered when Push-to-Talk has been enabled or disabled for desktop mode. - * @function Audio.pushToTalkDesktopChanged - * @param {boolean} enabled - true if Push-to-Talk is emabled for Desktop mode, otherwise false. - * @returns {Signal} - */ - void pushToTalkDesktopChanged(bool enabled); - - /** - * Triggered when Push-to-Talk has been enabled or disabled for HMD mode. - * @function Audio.pushToTalkHMDChanged - * @param {boolean} enabled - true if Push-to-Talk is emabled for HMD mode, otherwise false. - * @returns {Signal} - */ - void pushToTalkHMDChanged(bool enabled); - /**jsdoc * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged From 74320fe7be5c1ed0a8f9bc2160d1dceb3e523523 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Fri, 8 Mar 2019 17:24:43 -0800 Subject: [PATCH 109/132] Case 20622 - Qml Marketplace - Fix categories dropdown UI issues --- .../hifi/commerce/marketplace/Marketplace.qml | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml index 07ded49956..6f8150028a 100644 --- a/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml +++ b/interface/resources/qml/hifi/commerce/marketplace/Marketplace.qml @@ -382,6 +382,7 @@ Rectangle { id: categoriesDropdown anchors.fill: parent; + anchors.topMargin: 2 visible: false z: 10 @@ -420,6 +421,7 @@ Rectangle { model: categoriesModel delegate: ItemDelegate { + id: categoriesItemDelegate height: 34 width: parent.width @@ -431,6 +433,8 @@ Rectangle { color: hifi.colors.white visible: true + border.color: hifi.colors.blueHighlight + border.width: 0 RalewayRegular { id: categoriesItemText @@ -439,7 +443,7 @@ Rectangle { anchors.fill:parent text: model.name - color: ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.baseGray + color: categoriesItemDelegate.ListView.isCurrentItem ? hifi.colors.blueHighlight : hifi.colors.baseGray horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter size: 14 @@ -449,16 +453,22 @@ Rectangle { MouseArea { anchors.fill: parent z: 10 - hoverEnabled: true propagateComposedEvents: false - onEntered: { - categoriesItem.color = ListView.isCurrentItem ? hifi.colors.white : hifi.colors.lightBlueHighlight; + onPositionChanged: { + // Must use onPositionChanged and not onEntered + // due to a QML bug where a mouseenter event was + // being fired on open of the categories list even + // though the mouse was outside the borders + categoriesItem.border.width = 2; + } + onExited: { + categoriesItem.border.width = 0; } - onExited: { - categoriesItem.color = ListView.isCurrentItem ? hifi.colors.lightBlueHighlight : hifi.colors.white; + onCanceled: { + categoriesItem.border.width = 0; } onClicked: { From 3464fe09c1b0ea2b3671941e09bbe91dbe6f36bf Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Fri, 8 Mar 2019 17:39:11 -0800 Subject: [PATCH 110/132] Applying the hero changes to master soon to be rc81 --- .../src/avatars/AvatarMixerClientData.cpp | 4 + assignment-client/src/avatars/MixerAvatar.h | 3 - .../qml/+android_interface/Stats.qml | 4 + interface/resources/qml/Stats.qml | 4 + interface/src/avatar/AvatarManager.cpp | 183 +++++++++++------- interface/src/avatar/AvatarManager.h | 4 + interface/src/avatar/OtherAvatar.cpp | 12 -- interface/src/ui/Stats.cpp | 2 + interface/src/ui/Stats.h | 18 ++ libraries/avatars/src/AvatarData.cpp | 13 +- libraries/avatars/src/AvatarData.h | 18 +- 11 files changed, 179 insertions(+), 86 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index cef4383aee..557c5c9fe3 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -130,12 +130,16 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared } _lastReceivedSequenceNumber = sequenceNumber; glm::vec3 oldPosition = getPosition(); + bool oldHasPriority = _avatar->getHasPriority(); // compute the offset to the data payload if (!_avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()))) { return false; } + // Regardless of what the client says, restore the priority as we know it without triggering any update. + _avatar->setHasPriorityWithoutTimestampReset(oldHasPriority); + auto newPosition = getPosition(); if (newPosition != oldPosition) { //#define AVATAR_HERO_TEST_HACK diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index 4c3ded4582..3e80704495 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -19,11 +19,8 @@ class MixerAvatar : public AvatarData { public: - bool getHasPriority() const { return _hasPriority; } - void setHasPriority(bool hasPriority) { _hasPriority = hasPriority; } private: - bool _hasPriority { false }; }; using MixerAvatarSharedPointer = std::shared_ptr; diff --git a/interface/resources/qml/+android_interface/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml index fe56f3797b..54f6086a86 100644 --- a/interface/resources/qml/+android_interface/Stats.qml +++ b/interface/resources/qml/+android_interface/Stats.qml @@ -113,6 +113,10 @@ Item { visible: root.expanded text: "Avatars Updated: " + root.updatedAvatarCount } + StatText { + visible: root.expanded + text: "Heroes Count/Updated: " + root.heroAvatarCount + "/" + root.updatedHeroAvatarCount + } StatText { visible: root.expanded text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 3b703d72e6..6748418d19 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -115,6 +115,10 @@ Item { visible: root.expanded text: "Avatars Updated: " + root.updatedAvatarCount } + StatText { + visible: root.expanded + text: "Heroes Count/Updated: " + root.heroAvatarCount + "/" + root.updatedHeroAvatarCount + } StatText { visible: root.expanded text: "Avatars NOT Updated: " + root.notUpdatedAvatarCount diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 55025b3b23..c66c0a30cb 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -232,96 +232,142 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { auto avatarMap = getHashCopy(); const auto& views = qApp->getConicalViews(); - PrioritySortUtil::PriorityQueue sortedAvatars(views, - AvatarData::_avatarSortCoefficientSize, - AvatarData::_avatarSortCoefficientCenter, - AvatarData::_avatarSortCoefficientAge); - sortedAvatars.reserve(avatarMap.size() - 1); // don't include MyAvatar + // Prepare 2 queues for heros and for crowd avatars + using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue; + // Keep two independent queues, one for heroes and one for the riff-raff. + enum PriorityVariants + { + kHero = 0, + kNonHero, + NumVariants + }; + AvatarPriorityQueue avatarPriorityQueues[NumVariants] = { + { views, + AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, + AvatarData::_avatarSortCoefficientAge }, + { views, + AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, + AvatarData::_avatarSortCoefficientAge } }; + // Reserve space + //avatarPriorityQueues[kHero].reserve(10); // just few + avatarPriorityQueues[kNonHero].reserve(avatarMap.size() - 1); // don't include MyAvatar // Build vector and compute priorities auto nodeList = DependencyManager::get(); AvatarHash::iterator itr = avatarMap.begin(); while (itr != avatarMap.end()) { - const auto& avatar = std::static_pointer_cast(*itr); + auto avatar = std::static_pointer_cast(*itr); // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update or fade out uninitialized Avatars if (avatar != _myAvatar && avatar->isInitialized() && !nodeList->isPersonalMutingNode(avatar->getID())) { - sortedAvatars.push(SortableAvatar(avatar)); + if (avatar->getHasPriority()) { + avatarPriorityQueues[kHero].push(SortableAvatar(avatar)); + } else { + avatarPriorityQueues[kNonHero].push(SortableAvatar(avatar)); + } } ++itr; } - // Sort - const auto& sortedAvatarVector = sortedAvatars.getSortedVector(); + + _numHeroAvatars = (int)avatarPriorityQueues[kHero].size(); // process in sorted order uint64_t startTime = usecTimestampNow(); - uint64_t updateExpiry = startTime + MAX_UPDATE_AVATARS_TIME_BUDGET; + + const uint64_t MAX_UPDATE_HEROS_TIME_BUDGET = uint64_t(0.8 * MAX_UPDATE_AVATARS_TIME_BUDGET); + + uint64_t updatePriorityExpiries[NumVariants] = { startTime + MAX_UPDATE_HEROS_TIME_BUDGET, startTime + MAX_UPDATE_AVATARS_TIME_BUDGET }; + int numHerosUpdated = 0; int numAvatarsUpdated = 0; - int numAVatarsNotUpdated = 0; + int numAvatarsNotUpdated = 0; render::Transaction renderTransaction; workload::Transaction workloadTransaction; - 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(); - if (avatar->needsPhysicsUpdate()) { - _avatarsToChangeInPhysics.insert(avatar); - } - } else { - avatar->updateOrbPosition(); - } + + for (int p = kHero; p < NumVariants; p++) { + auto& priorityQueue = avatarPriorityQueues[p]; + // Sorting the current queue HERE as part of the measured timing. + const auto& sortedAvatarVector = priorityQueue.getSortedVector(); - // for ALL avatars... - if (_shouldRender) { - avatar->ensureInScene(avatar, qApp->getMain3DScene()); - } - avatar->animateScaleChanges(deltaTime); + auto passExpiry = updatePriorityExpiries[p]; - uint64_t now = usecTimestampNow(); - if (now < updateExpiry) { - // we're within budget - bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; - if (inView && avatar->hasNewJointData()) { - numAvatarsUpdated++; + 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); } - 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); - } - avatar->simulate(deltaTime, inView); - if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) { - _myAvatar->addAvatarHandsToFlow(avatar); - } - avatar->updateRenderItem(renderTransaction); - avatar->updateSpaceProxy(workloadTransaction); - avatar->setLastRenderUpdateTime(startTime); - } else { - // we've spent our full time budget --> bail on the rest of the avatar updates - // --> more avatars may freeze until their priority trickles up - // --> some scale animations may glitch - // --> some avatar velocity measurements may be a little off - - // no time to simulate, but we take the time to count how many were tragically missed - while (it != sortedAvatarVector.end()) { - const SortableAvatar& newSortData = *it; - const auto& newAvatar = newSortData.getAvatar(); - bool inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; - // Once we reach an avatar that's not in view, all avatars after it will also be out of view - if (!inView) { - break; + // 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(); + if (avatar->needsPhysicsUpdate()) { + _avatarsToChangeInPhysics.insert(avatar); } - numAVatarsNotUpdated += (int)(newAvatar->hasNewJointData()); - ++it; + } else { + avatar->updateOrbPosition(); } - break; + + // for ALL avatars... + if (_shouldRender) { + avatar->ensureInScene(avatar, qApp->getMain3DScene()); + } + + avatar->animateScaleChanges(deltaTime); + + uint64_t now = usecTimestampNow(); + if (now < passExpiry) { + // we're within budget + bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; + if (inView && avatar->hasNewJointData()) { + numAvatarsUpdated++; + } + 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); + } + avatar->simulate(deltaTime, inView); + if (avatar->getSkeletonModel()->isLoaded() && avatar->getWorkloadRegion() == workload::Region::R1) { + _myAvatar->addAvatarHandsToFlow(avatar); + } + avatar->updateRenderItem(renderTransaction); + avatar->updateSpaceProxy(workloadTransaction); + avatar->setLastRenderUpdateTime(startTime); + } else { + // we've spent our time budget for this priority bucket + // let's deal with the reminding avatars if this pass and BREAK from the for loop + + if (p == kHero) { + // Hero, + // --> put them back in the non hero queue + + auto& crowdQueue = avatarPriorityQueues[kNonHero]; + while (it != sortedAvatarVector.end()) { + crowdQueue.push(SortableAvatar((*it).getAvatar())); + ++it; + } + } else { + // Non Hero + // --> bail on the rest of the avatar updates + // --> more avatars may freeze until their priority trickles up + // --> some scale animations may glitch + // --> some avatar velocity measurements may be a little off + + // no time to simulate, but we take the time to count how many were tragically missed + numAvatarsNotUpdated = sortedAvatarVector.end() - it; + } + + // We had to cut short this pass, we must break out of the for loop here + break; + } + } + + if (p == kHero) { + numHerosUpdated = numAvatarsUpdated; } } @@ -337,7 +383,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { _space->enqueueTransaction(workloadTransaction); _numAvatarsUpdated = numAvatarsUpdated; - _numAvatarsNotUpdated = numAVatarsNotUpdated; + _numAvatarsNotUpdated = numAvatarsNotUpdated; + _numHeroAvatarsUpdated = numHerosUpdated; simulateAvatarFades(deltaTime); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 51352ec861..2b58b14d11 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -90,6 +90,8 @@ public: int getNumAvatarsUpdated() const { return _numAvatarsUpdated; } int getNumAvatarsNotUpdated() const { return _numAvatarsNotUpdated; } + int getNumHeroAvatars() const { return _numHeroAvatars; } + int getNumHeroAvatarsUpdated() const { return _numHeroAvatarsUpdated; } float getAvatarSimulationTime() const { return _avatarSimulationTime; } void updateMyAvatar(float deltaTime); @@ -242,6 +244,8 @@ private: RateCounter<> _myAvatarSendRate; int _numAvatarsUpdated { 0 }; int _numAvatarsNotUpdated { 0 }; + int _numHeroAvatars{ 0 }; + int _numHeroAvatarsUpdated{ 0 }; float _avatarSimulationTime { 0.0f }; bool _shouldRender { true }; bool _myAvatarDataPacketsPaused { false }; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 7848c46eee..11eb6542c4 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -200,17 +200,6 @@ void OtherAvatar::resetDetailedMotionStates() { void OtherAvatar::setWorkloadRegion(uint8_t region) { _workloadRegion = region; - QString printRegion = ""; - if (region == workload::Region::R1) { - printRegion = "R1"; - } else if (region == workload::Region::R2) { - printRegion = "R2"; - } else if (region == workload::Region::R3) { - printRegion = "R3"; - } else { - printRegion = "invalid"; - } - qCDebug(avatars) << "Setting workload region to " << printRegion; computeShapeLOD(); } @@ -235,7 +224,6 @@ void OtherAvatar::computeShapeLOD() { if (newLOD != _bodyLOD) { _bodyLOD = newLOD; if (isInPhysicsSimulation()) { - qCDebug(avatars) << "Changing to body LOD " << newLOD; _needsReinsertion = true; } } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index e3697ee8ec..ecdae0b375 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -125,8 +125,10 @@ void Stats::updateStats(bool force) { auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); + STAT_UPDATE(heroAvatarCount, avatarManager->getNumHeroAvatars()); STAT_UPDATE(physicsObjectCount, qApp->getNumCollisionObjects()); STAT_UPDATE(updatedAvatarCount, avatarManager->getNumAvatarsUpdated()); + STAT_UPDATE(updatedHeroAvatarCount, avatarManager->getNumHeroAvatarsUpdated()); STAT_UPDATE(notUpdatedAvatarCount, avatarManager->getNumAvatarsNotUpdated()); STAT_UPDATE(serverCount, (int)nodeList->size()); STAT_UPDATE_FLOAT(renderrate, qApp->getRenderLoopRate(), 0.1f); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 36e92b00af..0f563a6935 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -49,8 +49,10 @@ private: \ * @property {number} presentdroprate - Read-only. * @property {number} gameLoopRate - Read-only. * @property {number} avatarCount - Read-only. + * @property {number} heroAvatarCount - Read-only. * @property {number} physicsObjectCount - Read-only. * @property {number} updatedAvatarCount - Read-only. + * @property {number} updatedHeroAvatarCount - Read-only. * @property {number} notUpdatedAvatarCount - Read-only. * @property {number} packetInCount - Read-only. * @property {number} packetOutCount - Read-only. @@ -203,8 +205,10 @@ class Stats : public QQuickItem { STATS_PROPERTY(float, presentdroprate, 0) STATS_PROPERTY(int, gameLoopRate, 0) STATS_PROPERTY(int, avatarCount, 0) + STATS_PROPERTY(int, heroAvatarCount, 0) STATS_PROPERTY(int, physicsObjectCount, 0) STATS_PROPERTY(int, updatedAvatarCount, 0) + STATS_PROPERTY(int, updatedHeroAvatarCount, 0) STATS_PROPERTY(int, notUpdatedAvatarCount, 0) STATS_PROPERTY(int, packetInCount, 0) STATS_PROPERTY(int, packetOutCount, 0) @@ -436,6 +440,13 @@ signals: */ void avatarCountChanged(); + /**jsdoc + * Triggered when the value of the heroAvatarCount property changes. + * @function Stats.heroAvatarCountChanged + * @returns {Signal} + */ + void heroAvatarCountChanged(); + /**jsdoc * Triggered when the value of the updatedAvatarCount property changes. * @function Stats.updatedAvatarCountChanged @@ -443,6 +454,13 @@ signals: */ void updatedAvatarCountChanged(); + /**jsdoc + * Triggered when the value of the updatedHeroAvatarCount property changes. + * @function Stats.updatedHeroAvatarCountChanged + * @returns {Signal} + */ + void updatedHeroAvatarCountChanged(); + /**jsdoc * Triggered when the value of the notUpdatedAvatarCount property changes. * @function Stats.notUpdatedAvatarCountChanged diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index c733cfa291..26407c3564 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -564,6 +564,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent setAtBit16(flags, COLLIDE_WITH_OTHER_AVATARS); } + // Avatar has hero priority + if (getHasPriority()) { + setAtBit16(flags, HAS_HERO_PRIORITY); + } + data->flags = flags; destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); @@ -1152,7 +1157,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); auto newCollideWithOtherAvatars = oneAtBit16(bitItems, COLLIDE_WITH_OTHER_AVATARS); - + auto newHasPriority = oneAtBit16(bitItems, HAS_HERO_PRIORITY); + bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected); @@ -1161,8 +1167,10 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement); bool proceduralBlinkFaceMovementChanged = (_headData->getHasProceduralBlinkFaceMovement() != newHasProceduralBlinkFaceMovement); bool collideWithOtherAvatarsChanged = (_collideWithOtherAvatars != newCollideWithOtherAvatars); + bool hasPriorityChanged = (getHasPriority() != newHasPriority); bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged || - proceduralEyeFaceMovementChanged || proceduralBlinkFaceMovementChanged || collideWithOtherAvatarsChanged; + proceduralEyeFaceMovementChanged || + proceduralBlinkFaceMovementChanged || collideWithOtherAvatarsChanged || hasPriorityChanged; _keyState = newKeyState; _handState = newHandState; @@ -1172,6 +1180,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement); _headData->setHasProceduralBlinkFaceMovement(newHasProceduralBlinkFaceMovement); _collideWithOtherAvatars = newCollideWithOtherAvatars; + setHasPriorityWithoutTimestampReset(newHasPriority); sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 63396a59ac..95bbcbeb16 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -100,6 +100,9 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = // Procedural audio to mouth movement is enabled 8th bit // Procedural Blink is enabled 9th bit // Procedural Eyelid is enabled 10th bit +// Procedural PROCEDURAL_BLINK_FACE_MOVEMENT is enabled 11th bit +// Procedural Collide with other avatars is enabled 12th bit +// Procedural Has Hero Priority is enabled 13th bit const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits @@ -111,7 +114,7 @@ const int AUDIO_ENABLED_FACE_MOVEMENT = 8; // 9th bit const int PROCEDURAL_EYE_FACE_MOVEMENT = 9; // 10th bit const int PROCEDURAL_BLINK_FACE_MOVEMENT = 10; // 11th bit const int COLLIDE_WITH_OTHER_AVATARS = 11; // 12th bit - +const int HAS_HERO_PRIORITY = 12; // 13th bit (be scared) const char HAND_STATE_NULL = 0; const char LEFT_HAND_POINTING_FLAG = 1; @@ -1121,6 +1124,18 @@ public: int getAverageBytesReceivedPerSecond() const; int getReceiveRate() const; + // An Avatar can be set Priority from the AvatarMixer side. + bool getHasPriority() const { return _hasPriority; } + // regular setHasPriority does a check of state changed and if true reset 'additionalFlagsChanged' timestamp + void setHasPriority(bool hasPriority) { + if (_hasPriority != hasPriority) { + _additionalFlagsChanged = usecTimestampNow(); + _hasPriority = hasPriority; + } + } + // In some cases, we want to assign the hasPRiority flag without reseting timestamp + void setHasPriorityWithoutTimestampReset(bool hasPriority) { _hasPriority = hasPriority; } + const glm::vec3& getTargetVelocity() const { return _targetVelocity; } void clearRecordingBasis(); @@ -1498,6 +1513,7 @@ protected: bool _isNewAvatar { true }; bool _isClientAvatar { false }; bool _collideWithOtherAvatars { true }; + bool _hasPriority{ false }; // null unless MyAvatar or ScriptableAvatar sending traits data to mixer std::unique_ptr _clientTraitsHandler; From 04c6c425125d4da61863662ac83f1242c1496705 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Sun, 10 Mar 2019 14:28:31 -0700 Subject: [PATCH 111/132] Fix build error from merge. --- interface/src/scripting/Audio.cpp | 40 ------------------------------- 1 file changed, 40 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 0a859c4dcc..669856198d 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -211,12 +211,6 @@ void Audio::setPTTHMD(bool enabled) { } } -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - void Audio::saveData() { _desktopMutedSetting.set(getMutedDesktop()); _hmdMutedSetting.set(getMutedHMD()); @@ -237,40 +231,6 @@ bool Audio::getPTTHMD() const { }); } -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - -bool Audio::getPTTHMD() const { - return resultWithReadLock([&] { - return _pttHMD; - }); -} - -void Audio::saveData() { - _desktopMutedSetting.set(getMutedDesktop()); - _hmdMutedSetting.set(getMutedHMD()); - _pttDesktopSetting.set(getPTTDesktop()); - _pttHMDSetting.set(getPTTHMD()); -} - -void Audio::loadData() { - _desktopMuted = _desktopMutedSetting.get(); - _hmdMuted = _hmdMutedSetting.get(); - _pttDesktop = _pttDesktopSetting.get(); - _pttHMD = _pttHMDSetting.get(); -} - bool Audio::noiseReductionEnabled() const { return resultWithReadLock([&] { return _enableNoiseReduction; From 2eaa1e63d333a2872683001d04cb24e724ac1f40 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 17:07:02 -0700 Subject: [PATCH 112/132] switch from column layout to item --- interface/resources/qml/hifi/audio/Audio.qml | 74 +++----------------- 1 file changed, 8 insertions(+), 66 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 569cd23176..ce968090b4 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -166,10 +166,12 @@ Rectangle { Separator {} - ColumnLayout { - spacing: muteMic.spacing; + Item { + width: rightMostInputLevelPos + height: pttTextContainer.height + pttCheckBox.height + margins.paddings + 10 AudioControls.CheckBox { - spacing: muteMic.spacing + id: pttCheckBox + anchors.top: parent.top text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; onClicked: { @@ -189,69 +191,9 @@ Rectangle { } Item { id: pttTextContainer - x: margins.paddings; - width: rightMostInputLevelPos - height: pttTextMetrics.height - visible: true - TextMetrics { - id: pttTextMetrics - text: pttText.text - font: pttText.font - } - RalewayRegular { - id: pttText - wrapMode: Text.WordWrap - color: hifi.colors.white; - width: parent.width; - font.italic: true - size: 16; - text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : - qsTr("Press and hold the button \"T\" to unmute."); - onTextChanged: { - if (pttTextMetrics.width > rightMostInputLevelPos) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; - } else { - pttTextContainer.height = pttTextMetrics.height; - } - } - } - Component.onCompleted: { - if (pttTextMetrics.width > rightMostInputLevelPos) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; - } else { - pttTextContainer.height = pttTextMetrics.height; - } - } - } - } - - Separator {} - - ColumnLayout { - spacing: muteMic.spacing; - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Push To Talk (T)"); - checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; - onClicked: { - if (isVR) { - AudioScriptingInterface.pushToTalkHMD = checked; - } else { - AudioScriptingInterface.pushToTalkDesktop = checked; - } - checked = Qt.binding(function() { - if (isVR) { - return AudioScriptingInterface.pushToTalkHMD; - } else { - return AudioScriptingInterface.pushToTalkDesktop; - } - }); // restore binding - } - } - Item { - id: pttTextContainer - x: margins.paddings; - width: rightMostInputLevelPos + anchors.top: pttCheckBox.bottom + anchors.topMargin: 10 + width: parent.width height: pttTextMetrics.height visible: true TextMetrics { From 54973375a4f543557a7547828e3c8896db5bdb20 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 17:18:18 -0700 Subject: [PATCH 113/132] fixng ptt checkbox --- interface/resources/qml/hifi/audio/Audio.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index ce968090b4..46ae937614 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -171,6 +171,8 @@ Rectangle { height: pttTextContainer.height + pttCheckBox.height + margins.paddings + 10 AudioControls.CheckBox { id: pttCheckBox + spacing: muteMic.spacing; + width: rightMostInputLevelPos anchors.top: parent.top text: qsTr("Push To Talk (T)"); checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; From cf3694e8e343f0dd39e8875c689214ea4611ed33 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 19:00:41 -0700 Subject: [PATCH 114/132] fixing error in master + getting ptt to show up --- interface/resources/qml/hifi/audio/Audio.qml | 114 +++++++++---------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index a07fb1cb95..376aa39269 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -104,7 +104,6 @@ Rectangle { RowLayout { x: 2 * margins.paddings; - spacing: columnOne.width; width: parent.width; // mute is in its own row @@ -170,66 +169,66 @@ Rectangle { } } } + } - Separator {} + Separator {} - Item { - width: rightMostInputLevelPos - height: pttTextContainer.height + pttCheckBox.height + margins.paddings + 10 - AudioControls.CheckBox { - id: pttCheckBox - spacing: muteMic.spacing; - width: rightMostInputLevelPos - anchors.top: parent.top - text: qsTr("Push To Talk (T)"); - checked: isVR ? AudioScriptingInterface.pushToTalkHMD : AudioScriptingInterface.pushToTalkDesktop; - onClicked: { - if (isVR) { - AudioScriptingInterface.pushToTalkHMD = checked; + + ColumnLayout { + id: pttColumn + spacing: 24; + x: 2 * margins.paddings; + HifiControlsUit.Switch { + id: pttSwitch + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Push To Talk (T)"); + backgroundOnColor: "#E3E3E3"; + checked: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; + onCheckedChanged: { + if ((bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR)) { + AudioScriptingInterface.pushToTalkDesktop = checked; + } else { + AudioScriptingInterface.pushToTalkHMD = checked; + } + checked = Qt.binding(function() { + if ((bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR)) { + return AudioScriptingInterface.pushToTalkDesktop; } else { - AudioScriptingInterface.pushToTalkDesktop = checked; + return AudioScriptingInterface.pushToTalkHMD; } - checked = Qt.binding(function() { - if (isVR) { - return AudioScriptingInterface.pushToTalkHMD; - } else { - return AudioScriptingInterface.pushToTalkDesktop; - } - }); // restore binding - } + }); // restore binding } - Item { - id: pttTextContainer - anchors.top: pttCheckBox.bottom - anchors.topMargin: 10 - width: parent.width - height: pttTextMetrics.height - visible: true - TextMetrics { - id: pttTextMetrics - text: pttText.text - font: pttText.font - } - RalewayRegular { - id: pttText - wrapMode: Text.WordWrap - color: hifi.colors.white; - width: parent.width; - font.italic: true - size: 16; - text: isVR ? qsTr("Press and hold grip triggers on both of your controllers to unmute.") : - qsTr("Press and hold the button \"T\" to unmute."); - onTextChanged: { - if (pttTextMetrics.width > rightMostInputLevelPos) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; - } else { - pttTextContainer.height = pttTextMetrics.height; - } - } - } - Component.onCompleted: { - if (pttTextMetrics.width > rightMostInputLevelPos) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / rightMostInputLevelPos) * pttTextMetrics.height; + } + Item { + id: pttTextContainer + width: rightMostInputLevelPos + height: pttTextMetrics.height + anchors.left: parent.left + anchors.leftMargin: -margins.padding + TextMetrics { + id: pttTextMetrics + text: pttText.text + font: pttText.font + } + RalewayRegular { + id: pttText + color: hifi.colors.white; + width: parent.width; + wrapMode: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR) ? Text.NoWrap : Text.WordWrap; + font.italic: true + size: 16; + + text: (bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR) ? qsTr("Press and hold the button \"T\" to unmute.") : + qsTr("Press and hold grip triggers on both of your controllers to unmute."); + onTextChanged: { + if (pttTextMetrics.width > pttTextContainer.width) { + pttTextContainer.height = Math.ceil(pttTextMetrics.width / pttTextContainer.width) * pttTextMetrics.height; } else { pttTextContainer.height = pttTextMetrics.height; } @@ -240,6 +239,7 @@ Rectangle { Separator {} + Item { x: margins.paddings; width: parent.width - margins.paddings*2 @@ -293,7 +293,7 @@ Rectangle { text: devicename onPressed: { if (!checked) { - stereoMic.checked = false; + stereoInput.checked = false; AudioScriptingInterface.setStereoInput(false); // the next selected audio device might not support stereo AudioScriptingInterface.setInputDevice(info, bar.currentIndex === 1); } From 20487b2ad1adb1a10b16bac8cb48e3bf12e93e44 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 19:39:45 -0700 Subject: [PATCH 115/132] connecting pushingToTalkChanged with handlePushedToTalk --- interface/src/Application.cpp | 19 +++++++++---------- interface/src/Application.h | 2 -- interface/src/scripting/Audio.cpp | 3 ++- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fc9fcd1bbb..215736001c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1435,8 +1435,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); connect(this, &Application::activeDisplayPluginChanged, reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); - connect(this, &Application::pushedToTalk, - reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::handlePushedToTalk); } // Create the rendering engine. This can be slow on some machines due to lots of @@ -1609,13 +1607,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo bool navAxis = false; switch (actionEnum) { case Action::TOGGLE_PUSHTOTALK: - if (audioScriptingInterface->getPTT()) { - if (state > 0.0f) { - audioScriptingInterface->setPushingToTalk(false); - } else if (state < 0.0f) { - audioScriptingInterface->setPushingToTalk(true); - } + if (state > 0.0f) { + audioScriptingInterface->setPushingToTalk(false); + } else if (state < 0.0f) { + audioScriptingInterface->setPushingToTalk(true); } + break; case Action::UI_NAV_VERTICAL: navAxis = true; @@ -4218,7 +4215,8 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_T: - emit pushedToTalk(true); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->setPushingToTalk(true); break; case Qt::Key_P: { @@ -4329,7 +4327,8 @@ void Application::keyReleaseEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_T: - emit pushedToTalk(false); + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); + audioScriptingInterface->setPushingToTalk(false); break; } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 1c86326f90..a8cc9450c5 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -358,8 +358,6 @@ signals: void miniTabletEnabledChanged(bool enabled); - void pushedToTalk(bool enabled); - public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 669856198d..c4dfcffb61 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -40,6 +40,8 @@ Audio::Audio() : _devices(_contextIsHMD) { connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); + // when pushing to talk changed, handle it. + connect(this, &Audio::pushingToTalkChanged, this, &Audio::handlePushedToTalk); enableNoiseReduction(enableNoiseReductionSetting.get()); onContextChanged(); } @@ -344,7 +346,6 @@ void Audio::handlePushedToTalk(bool enabled) { } else { setMuted(true); } - setPushingToTalk(enabled); } } From 7b51061b5f3eee889b6d5f7ea4cec5e502518650 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Sun, 10 Mar 2019 19:46:01 -0700 Subject: [PATCH 116/132] fixing initialization in switch statement --- interface/src/Application.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 215736001c..3230419816 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4047,6 +4047,7 @@ void Application::keyPressEvent(QKeyEvent* event) { _keysPressed.insert(event->key(), *event); } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isKeyCaptured(event) || isInterstitialMode()) { @@ -4215,7 +4216,6 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_T: - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); audioScriptingInterface->setPushingToTalk(true); break; @@ -4325,9 +4325,9 @@ void Application::keyReleaseEvent(QKeyEvent* event) { _keyboardMouseDevice->keyReleaseEvent(event); } + auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); switch (event->key()) { case Qt::Key_T: - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); audioScriptingInterface->setPushingToTalk(false); break; } From ec0cf3ee3ace28f9c2bc39fa350190238f15a46c Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Mon, 11 Mar 2019 09:58:20 -0700 Subject: [PATCH 117/132] Fix typo. --- .../controllerModules/pushToTalk.js | 123 +++++++++--------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index 9d6435f497..6b1bacc367 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -11,65 +11,66 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); -(function() { // BEGIN LOCAL_SCOPE - function PushToTalkHandler() { - var _this = this; - this.active = false; - //var pttMapping, mappingName; - - this.setup = function() { - //mappingName = 'Hifi-PTT-Dev-' + Math.random(); - //pttMapping = Controller.newMapping(mappingName); - //pttMapping.enable(); - }; - - this.shouldTalk = function (controllerData) { - // Set up test against controllerData here... - var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; - return (gripVal) ? true : false; - }; - - this.shouldStopTalking = function (controllerData) { - var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; - return (gripVal) ? false : true; - }; - - this.isReady = function (controllerData, deltaTime) { - if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { - Audio.pushingToTalk = true; - returnMakeRunningValues(true, [], []); - } - - return makeRunningValues(false, [], []); - }; - - this.run = function (controllerData, deltaTime) { - if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { - Audio.pushingToTalk = false; - return makeRunningValues(false, [], []); - } - - return makeRunningValues(true, [], []); - }; - - this.cleanup = function () { - //pttMapping.disable(); - }; - - this.parameters = makeDispatcherModuleParameters( - 950, - ["head"], - [], - 100); - } - - var pushToTalk = new PushToTalkHandler(); - enableDispatcherModule("PushToTalk", pushToTalk); - - function cleanup () { - pushToTalk.cleanup(); - disableDispatcherModule("PushToTalk"); - }; - - Script.scriptEnding.connect(cleanup); +(function () { // BEGIN LOCAL_SCOPE + function PushToTalkHandler() { + var _this = this; + this.active = false; + //var pttMapping, mappingName; + + this.setup = function () { + //mappingName = 'Hifi-PTT-Dev-' + Math.random(); + //pttMapping = Controller.newMapping(mappingName); + //pttMapping.enable(); + }; + + this.shouldTalk = function (controllerData) { + // Set up test against controllerData here... + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; + return (gripVal) ? true : false; + }; + + this.shouldStopTalking = function (controllerData) { + var gripVal = controllerData.secondaryValues[LEFT_HAND] && controllerData.secondaryValues[RIGHT_HAND]; + return (gripVal) ? false : true; + }; + + this.isReady = function (controllerData, deltaTime) { + if (HMD.active && Audio.pushToTalk && this.shouldTalk(controllerData)) { + Audio.pushingToTalk = true; + return makeRunningValues(true, [], []); + } + + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + if (this.shouldStopTalking(controllerData) || !Audio.pushToTalk) { + Audio.pushingToTalk = false; + print("Stop pushing to talk."); + return makeRunningValues(false, [], []); + } + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + //pttMapping.disable(); + }; + + this.parameters = makeDispatcherModuleParameters( + 950, + ["head"], + [], + 100); + } + + var pushToTalk = new PushToTalkHandler(); + enableDispatcherModule("PushToTalk", pushToTalk); + + function cleanup() { + pushToTalk.cleanup(); + disableDispatcherModule("PushToTalk"); + }; + + Script.scriptEnding.connect(cleanup); }()); // END LOCAL_SCOPE From c1ed01115de29730e9d2132d5f63e13d143f04a3 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Mon, 11 Mar 2019 09:58:35 -0700 Subject: [PATCH 118/132] Fix logic for HMD vs Desktop. --- interface/resources/qml/hifi/audio/Audio.qml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 376aa39269..ecc3297d9f 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -184,18 +184,15 @@ Rectangle { switchWidth: root.switchWidth; labelTextOn: qsTr("Push To Talk (T)"); backgroundOnColor: "#E3E3E3"; - checked: (bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; + checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; onCheckedChanged: { - if ((bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR)) { + if (bar.currentIndex === 0) { AudioScriptingInterface.pushToTalkDesktop = checked; } else { AudioScriptingInterface.pushToTalkHMD = checked; } checked = Qt.binding(function() { - if ((bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR)) { + if (bar.currentIndex === 0) { return AudioScriptingInterface.pushToTalkDesktop; } else { return AudioScriptingInterface.pushToTalkHMD; @@ -218,13 +215,11 @@ Rectangle { id: pttText color: hifi.colors.white; width: parent.width; - wrapMode: (bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR) ? Text.NoWrap : Text.WordWrap; + wrapMode: (bar.currentIndex === 0) ? Text.NoWrap : Text.WordWrap; font.italic: true size: 16; - text: (bar.currentIndex === 1 && isVR) || - (bar.currentIndex === 0 && !isVR) ? qsTr("Press and hold the button \"T\" to unmute.") : + text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to unmute.") : qsTr("Press and hold grip triggers on both of your controllers to unmute."); onTextChanged: { if (pttTextMetrics.width > pttTextContainer.width) { From e4e8a61328d1a2ec9c70608e216b8e4dbf1625b2 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Mon, 11 Mar 2019 10:46:51 -0700 Subject: [PATCH 119/132] Case 21326 - missing marketplaceInject.js The re-addition of marketplaceInject.js didn't merge from 80 for some reason. --- scripts/system/html/js/marketplacesInject.js | 744 +++++++++++++++++++ 1 file changed, 744 insertions(+) create mode 100644 scripts/system/html/js/marketplacesInject.js diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js new file mode 100644 index 0000000000..8d408169ba --- /dev/null +++ b/scripts/system/html/js/marketplacesInject.js @@ -0,0 +1,744 @@ +/* global $, window, MutationObserver */ + +// +// marketplacesInject.js +// +// Created by David Rowe on 12 Nov 2016. +// Copyright 2016 High Fidelity, Inc. +// +// Injected into marketplace Web pages. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function () { + // Event bridge messages. + var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; + var CLARA_IO_STATUS = "CLARA.IO STATUS"; + var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD"; + var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD"; + var GOTO_DIRECTORY = "GOTO_DIRECTORY"; + var GOTO_MARKETPLACE = "GOTO_MARKETPLACE"; + var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS"; + var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS"; + var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS"; + + var canWriteAssets = false; + var xmlHttpRequest = null; + var isPreparing = false; // Explicitly track download request status. + + var limitedCommerce = false; + var commerceMode = false; + var userIsLoggedIn = false; + var walletNeedsSetup = false; + var marketplaceBaseURL = "https://highfidelity.com"; + var messagesWaiting = false; + + function injectCommonCode(isDirectoryPage) { + // Supporting styles from marketplaces.css. + // Glyph font family, size, and spacing adjusted because HiFi-Glyphs cannot be used cross-domain. + $("head").append( + '' + ); + + // Supporting styles from edit-style.css. + // Font family, size, and position adjusted because Raleway-Bold cannot be used cross-domain. + $("head").append( + '' + ); + + // Footer. + var isInitialHiFiPage = location.href === (marketplaceBaseURL + "/marketplace?"); + $("body").append( + '

' + + (!isInitialHiFiPage ? '' : '') + + (isInitialHiFiPage ? '🛈 Get items from Clara.io!' : '') + + (!isDirectoryPage ? '' : '') + + (isDirectoryPage ? '🛈 Select a marketplace to explore.' : '') + + '
' + ); + + // Footer actions. + $("#back-button").on("click", function () { + if (document.referrer !== "") { + window.history.back(); + } else { + var params = { type: GOTO_MARKETPLACE }; + var itemIdMatch = location.search.match(/itemId=([^&]*)/); + if (itemIdMatch && itemIdMatch.length === 2) { + params.itemId = itemIdMatch[1]; + } + EventBridge.emitWebEvent(JSON.stringify(params)); + } + }); + $("#all-markets").on("click", function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_DIRECTORY + })); + }); + } + + function injectDirectoryCode() { + + // Remove e-mail hyperlink. + var letUsKnow = $("#letUsKnow"); + letUsKnow.replaceWith(letUsKnow.html()); + + // Add button links. + + $('#exploreClaraMarketplace').on('click', function () { + window.location = "https://clara.io/library?gameCheck=true&public=true"; + }); + $('#exploreHifiMarketplace').on('click', function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: GOTO_MARKETPLACE + })); + }); + } + + emitWalletSetupEvent = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "WALLET_SETUP" + })); + }; + + function maybeAddSetupWalletButton() { + if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) { + $('body').addClass("walletsetup-injected"); + + var resultsElement = document.getElementById('results'); + var setupWalletElement = document.createElement('div'); + setupWalletElement.classList.add("row"); + setupWalletElement.id = "setupWalletDiv"; + setupWalletElement.style = "height:60px;margin:20px 10px 10px 10px;padding:12px 5px;" + + "background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;"; + + var span = document.createElement('span'); + span.style = "margin:10px 5px;color:#1b6420;font-size:15px;"; + span.innerHTML = "Activate your Wallet to get money and shop in Marketplace."; + + var xButton = document.createElement('a'); + xButton.id = "xButton"; + xButton.setAttribute('href', "#"); + xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;"; + xButton.innerHTML = "X"; + xButton.onclick = function () { + setupWalletElement.remove(); + dummyRow.remove(); + }; + + setupWalletElement.appendChild(span); + setupWalletElement.appendChild(xButton); + + resultsElement.insertBefore(setupWalletElement, resultsElement.firstChild); + + // Dummy row for padding + var dummyRow = document.createElement('div'); + dummyRow.classList.add("row"); + dummyRow.style = "height:15px;"; + resultsElement.insertBefore(dummyRow, resultsElement.firstChild); + } + } + + function maybeAddLogInButton() { + if (!$('body').hasClass("login-injected") && !userIsLoggedIn) { + $('body').addClass("login-injected"); + var resultsElement = document.getElementById('results'); + if (!resultsElement) { // If we're on the main page, this will evaluate to `true` + resultsElement = document.getElementById('item-show'); + resultsElement.style = 'margin-top:0;'; + } + var logInElement = document.createElement('div'); + logInElement.classList.add("row"); + logInElement.id = "logInDiv"; + logInElement.style = "height:60px;margin:20px 10px 10px 10px;padding:5px;" + + "background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;"; + + var button = document.createElement('a'); + button.classList.add("btn"); + button.classList.add("btn-default"); + button.id = "logInButton"; + button.setAttribute('href', "#"); + button.innerHTML = "LOG IN"; + button.style = "width:80px;height:100%;margin-top:0;margin-left:10px;padding:13px;font-weight:bold;background:linear-gradient(white, #ccc);"; + button.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "LOGIN" + })); + }; + + var span = document.createElement('span'); + span.style = "margin:10px;color:#1b6420;font-size:15px;"; + span.innerHTML = "to get items from the Marketplace."; + + var xButton = document.createElement('a'); + xButton.id = "xButton"; + xButton.setAttribute('href', "#"); + xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;"; + xButton.innerHTML = "X"; + xButton.onclick = function () { + logInElement.remove(); + dummyRow.remove(); + }; + + logInElement.appendChild(button); + logInElement.appendChild(span); + logInElement.appendChild(xButton); + + resultsElement.insertBefore(logInElement, resultsElement.firstChild); + + // Dummy row for padding + var dummyRow = document.createElement('div'); + dummyRow.classList.add("row"); + dummyRow.style = "height:15px;"; + resultsElement.insertBefore(dummyRow, resultsElement.firstChild); + } + } + + function changeDropdownMenu() { + var logInOrOutButton = document.createElement('a'); + logInOrOutButton.id = "logInOrOutButton"; + logInOrOutButton.setAttribute('href', "#"); + logInOrOutButton.innerHTML = userIsLoggedIn ? "Log Out" : "Log In"; + logInOrOutButton.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "LOGIN" + })); + }; + + $($('.dropdown-menu').find('li')[0]).append(logInOrOutButton); + + $('a[href="/marketplace?view=mine"]').each(function () { + $(this).attr('href', '#'); + $(this).on('click', function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "MY_ITEMS" + })); + }); + }); + } + + function buyButtonClicked(id, referrer, edition) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "CHECKOUT", + itemId: id, + referrer: referrer, + itemEdition: edition + })); + } + + function injectBuyButtonOnMainPage() { + var cost; + + // Unbind original mouseenter and mouseleave behavior + $('body').off('mouseenter', '#price-or-edit .price'); + $('body').off('mouseleave', '#price-or-edit .price'); + + $('.grid-item').find('#price-or-edit').each(function () { + $(this).css({ "margin-top": "0" }); + }); + + $('.grid-item').find('#price-or-edit').find('a').each(function() { + if ($(this).attr('href') !== '#') { // Guard necessary because of the AJAX nature of Marketplace site + $(this).attr('data-href', $(this).attr('href')); + $(this).attr('href', '#'); + } + cost = $(this).closest('.col-xs-3').find('.item-cost').text(); + var costInt = parseInt(cost, 10); + + $(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6'); + $(this).closest('.col-xs-3').attr("class", 'col-xs-6'); + + var priceElement = $(this).find('.price'); + var available = true; + + if (priceElement.text() === 'invalidated' || + priceElement.text() === 'sold out' || + priceElement.text() === 'not for sale') { + available = false; + priceElement.css({ + "padding": "3px 5px 10px 5px", + "height": "40px", + "background": "linear-gradient(#a2a2a2, #fefefe)", + "color": "#000", + "font-weight": "600", + "line-height": "34px" + }); + } else { + priceElement.css({ + "padding": "3px 5px", + "height": "40px", + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "line-height": "34px" + }); + } + + if (parseInt(cost) > 0) { + priceElement.css({ "width": "auto" }); + + if (available) { + priceElement.html(' ' + cost); + } + + priceElement.css({ "min-width": priceElement.width() + 30 }); + } + }); + + // change pricing to GET/BUY on button hover + $('body').on('mouseenter', '#price-or-edit .price', function () { + var $this = $(this); + var buyString = "BUY"; + var getString = "GET"; + // Protection against the button getting stuck in the "BUY"/"GET" state. + // That happens when the browser gets two MOUSEENTER events before getting a + // MOUSELEAVE event. Also, if not available for sale, just return. + if ($this.text() === buyString || + $this.text() === getString || + $this.text() === 'invalidated' || + $this.text() === 'sold out' || + $this.text() === 'not for sale' ) { + return; + } + $this.data('initialHtml', $this.html()); + + var cost = $(this).parent().siblings().text(); + if (parseInt(cost) > 0) { + $this.text(buyString); + } + if (parseInt(cost) == 0) { + $this.text(getString); + } + }); + + $('body').on('mouseleave', '#price-or-edit .price', function () { + var $this = $(this); + $this.html($this.data('initialHtml')); + }); + + + $('.grid-item').find('#price-or-edit').find('a').on('click', function () { + var price = $(this).closest('.grid-item').find('.price').text(); + if (price === 'invalidated' || + price === 'sold out' || + price === 'not for sale') { + return false; + } + buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'), + "mainPage", + -1); + }); + } + + function injectUnfocusOnSearch() { + // unfocus input field on search, thus hiding virtual keyboard + $('#search-box').on('submit', function () { + if (document.activeElement) { + document.activeElement.blur(); + } + }); + } + + // fix for 10108 - marketplace category cannot scroll + function injectAddScrollbarToCategories() { + $('#categories-dropdown').on('show.bs.dropdown', function () { + $('body > div.container').css('display', 'none') + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' }); + }); + + $('#categories-dropdown').on('hide.bs.dropdown', function () { + $('body > div.container').css('display', ''); + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' }); + }); + } + + function injectHiFiCode() { + if (commerceMode) { + maybeAddLogInButton(); + maybeAddSetupWalletButton(); + + if (!$('body').hasClass("code-injected")) { + + $('body').addClass("code-injected"); + changeDropdownMenu(); + + var target = document.getElementById('templated-items'); + // MutationObserver is necessary because the DOM is populated after the page is loaded. + // We're searching for changes to the element whose ID is '#templated-items' - this is + // the element that gets filled in by the AJAX. + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + injectBuyButtonOnMainPage(); + }); + }); + var config = { attributes: true, childList: true, characterData: true }; + observer.observe(target, config); + + // Try this here in case it works (it will if the user just pressed the "back" button, + // since that doesn't trigger another AJAX request. + injectBuyButtonOnMainPage(); + } + } + + injectUnfocusOnSearch(); + injectAddScrollbarToCategories(); + } + + function injectHiFiItemPageCode() { + if (commerceMode) { + maybeAddLogInButton(); + + if (!$('body').hasClass("code-injected")) { + + $('body').addClass("code-injected"); + changeDropdownMenu(); + + var purchaseButton = $('#side-info').find('.btn').first(); + + var href = purchaseButton.attr('href'); + purchaseButton.attr('href', '#'); + var cost = $('.item-cost').text(); + var costInt = parseInt(cost, 10); + var availability = $.trim($('.item-availability').text()); + if (limitedCommerce && (costInt > 0)) { + availability = ''; + } + if (availability === 'available') { + purchaseButton.css({ + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "padding-bottom": "10px" + }); + } else { + purchaseButton.css({ + "background": "linear-gradient(#a2a2a2, #fefefe)", + "color": "#000", + "font-weight": "600", + "padding-bottom": "10px" + }); + } + + var type = $('.item-type').text(); + var isUpdating = window.location.href.indexOf('edition=') > -1; + var urlParams = new URLSearchParams(window.location.search); + if (isUpdating) { + purchaseButton.html('UPDATE FOR FREE'); + } else if (availability !== 'available') { + purchaseButton.html('UNAVAILABLE ' + (availability ? ('(' + availability + ')') : '')); + } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + purchaseButton.html('PURCHASE ' + cost); + } + + purchaseButton.on('click', function () { + if ('available' === availability || isUpdating) { + buyButtonClicked(window.location.pathname.split("/")[3], + "itemPage", + urlParams.get('edition')); + } + }); + } + } + + injectUnfocusOnSearch(); + } + + function updateClaraCode() { + // Have to repeatedly update Clara page because its content can change dynamically without location.href changing. + + // Clara library page. + if (location.href.indexOf("clara.io/library") !== -1) { + // Make entries navigate to "Image" view instead of default "Real Time" view. + var elements = $("a.thumbnail"); + for (var i = 0, length = elements.length; i < length; i++) { + var value = elements[i].getAttribute("href"); + if (value.slice(-6) !== "/image") { + elements[i].setAttribute("href", value + "/image"); + } + } + } + + // Clara item page. + if (location.href.indexOf("clara.io/view/") !== -1) { + // Make site navigation links retain gameCheck etc. parameters. + var element = $("a[href^=\'/library\']")[0]; + var parameters = "?gameCheck=true&public=true"; + var href = element.getAttribute("href"); + if (href.slice(-parameters.length) !== parameters) { + element.setAttribute("href", href + parameters); + } + + // Remove unwanted buttons and replace download options with a single "Download to High Fidelity" button. + var buttons = $("a.embed-button").parent("div"); + var downloadFBX; + if (buttons.find("div.btn-group").length > 0) { + buttons.children(".btn-primary, .btn-group , .embed-button").each(function () { this.remove(); }); + if ($("#hifi-download-container").length === 0) { // Button hasn't been moved already. + downloadFBX = $(' Download to High Fidelity'); + buttons.prepend(downloadFBX); + downloadFBX[0].addEventListener("click", startAutoDownload); + } + } + + // Move the "Download to High Fidelity" button to be more visible on tablet. + if ($("#hifi-download-container").length === 0 && window.innerWidth < 700) { + var downloadContainer = $('
'); + $(".top-title .col-sm-4").append(downloadContainer); + downloadContainer.append(downloadFBX); + } + } + } + + // Automatic download to High Fidelity. + function startAutoDownload() { + // One file request at a time. + if (isPreparing) { + console.log("WARNING: Clara.io FBX: Prepare only one download at a time"); + return; + } + + // User must be able to write to Asset Server. + if (!canWriteAssets) { + console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server"); + EventBridge.emitWebEvent(JSON.stringify({ + type: WARN_USER_NO_PERMISSIONS + })); + return; + } + + // User must be logged in. + var loginButton = $("#topnav a[href='/signup']"); + if (loginButton.length > 0) { + loginButton[0].click(); + return; + } + + // Obtain zip file to download for requested asset. + // Reference: https://clara.io/learn/sdk/api/export + + //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; + // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to + // be successful in generating zip files. + var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL"; + + var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1]; + var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid); + + xmlHttpRequest = new XMLHttpRequest(); + var responseTextIndex = 0; + var zipFileURL = ""; + + xmlHttpRequest.onreadystatechange = function () { + // Messages are appended to responseText; process the new ones. + var message = this.responseText.slice(responseTextIndex); + var statusMessage = ""; + + if (isPreparing) { // Ignore messages in flight after finished/cancelled. + var lines = message.split(/[\n\r]+/); + + for (var i = 0, length = lines.length; i < length; i++) { + if (lines[i].slice(0, 5) === "data:") { + // Parse line. + var data; + try { + data = JSON.parse(lines[i].slice(5)); + } + catch (e) { + data = {}; + } + + // Extract zip file URL. + if (data.hasOwnProperty("files") && data.files.length > 0) { + zipFileURL = data.files[0].url; + } + } + } + + if (statusMessage !== "") { + // Update the UI with the most recent status message. + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: statusMessage + })); + } + } + + responseTextIndex = this.responseText.length; + }; + + // Note: onprogress doesn't have computable total length so can't use it to determine % complete. + + xmlHttpRequest.onload = function () { + var statusMessage = ""; + + if (!isPreparing) { + return; + } + + isPreparing = false; + + var HTTP_OK = 200; + if (this.status !== HTTP_OK) { + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: statusMessage + })); + } else if (zipFileURL.slice(-4) !== ".zip") { + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: (statusMessage + ": " + zipFileURL) + })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_DOWNLOAD + })); + } + + xmlHttpRequest = null; + } + + isPreparing = true; + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_STATUS, + status: "Initiating download" + })); + + xmlHttpRequest.open("POST", url, true); + xmlHttpRequest.setRequestHeader("Accept", "text/event-stream"); + xmlHttpRequest.send(); + } + + function injectClaraCode() { + + // Make space for marketplaces footer in Clara pages. + $("head").append( + '' + ); + + // Condense space. + $("head").append( + '' + ); + + // Move "Download to High Fidelity" button. + $("head").append( + '' + ); + + // Update code injected per page displayed. + var updateClaraCodeInterval = undefined; + updateClaraCode(); + updateClaraCodeInterval = setInterval(function () { + updateClaraCode(); + }, 1000); + + window.addEventListener("unload", function () { + clearInterval(updateClaraCodeInterval); + updateClaraCodeInterval = undefined; + }); + + EventBridge.emitWebEvent(JSON.stringify({ + type: QUERY_CAN_WRITE_ASSETS + })); + } + + function cancelClaraDownload() { + isPreparing = false; + + if (xmlHttpRequest) { + xmlHttpRequest.abort(); + xmlHttpRequest = null; + console.log("Clara.io FBX: File download cancelled"); + EventBridge.emitWebEvent(JSON.stringify({ + type: CLARA_IO_CANCELLED_DOWNLOAD + })); + } + } + + function injectCode() { + var DIRECTORY = 0; + var HIFI = 1; + var CLARA = 2; + var HIFI_ITEM_PAGE = 3; + var pageType = DIRECTORY; + + if (location.href.indexOf(marketplaceBaseURL + "/") !== -1) { pageType = HIFI; } + if (location.href.indexOf("clara.io/") !== -1) { pageType = CLARA; } + if (location.href.indexOf(marketplaceBaseURL + "/marketplace/items/") !== -1) { pageType = HIFI_ITEM_PAGE; } + + injectCommonCode(pageType === DIRECTORY); + switch (pageType) { + case DIRECTORY: + injectDirectoryCode(); + break; + case HIFI: + injectHiFiCode(); + break; + case CLARA: + injectClaraCode(); + break; + case HIFI_ITEM_PAGE: + injectHiFiItemPageCode(); + break; + + } + } + + function onLoad() { + EventBridge.scriptEventReceived.connect(function (message) { + message = JSON.parse(message); + if (message.type === CAN_WRITE_ASSETS) { + canWriteAssets = message.canWriteAssets; + } else if (message.type === CLARA_IO_CANCEL_DOWNLOAD) { + cancelClaraDownload(); + } else if (message.type === "marketplaces") { + if (message.action === "commerceSetting") { + limitedCommerce = !!message.data.limitedCommerce; + commerceMode = !!message.data.commerceMode; + userIsLoggedIn = !!message.data.userIsLoggedIn; + walletNeedsSetup = !!message.data.walletNeedsSetup; + marketplaceBaseURL = message.data.metaverseServerURL; + if (marketplaceBaseURL.indexOf('metaverse.') !== -1) { + marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', ''); + } + messagesWaiting = message.data.messagesWaiting; + injectCode(); + } + } + }); + + // Request commerce setting + // Code is injected into the webpage after the setting comes back. + EventBridge.emitWebEvent(JSON.stringify({ + type: "REQUEST_SETTING" + })); + } + + // Load / unload. + window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). + window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed +}()); From 8aedc98a584870156998ba615bd935511621e147 Mon Sep 17 00:00:00 2001 From: r3tk0n Date: Mon, 11 Mar 2019 10:54:48 -0700 Subject: [PATCH 120/132] Fix QML formatting issue. --- interface/resources/qml/hifi/audio/Audio.qml | 57 ++++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index ecc3297d9f..5e849acedf 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -171,15 +171,14 @@ Rectangle { } } - Separator {} - - - ColumnLayout { - id: pttColumn - spacing: 24; - x: 2 * margins.paddings; + Separator { id: pttStartSeparator; } + Item { + width: rightMostInputLevelPos; + height: pttSwitch.height + pttText.height + 24; HifiControlsUit.Switch { id: pttSwitch + x: 2 * margins.paddings; + anchors.top: parent.top; height: root.switchHeight; switchWidth: root.switchWidth; labelTextOn: qsTr("Push To Talk (T)"); @@ -200,39 +199,25 @@ Rectangle { }); // restore binding } } - Item { - id: pttTextContainer - width: rightMostInputLevelPos - height: pttTextMetrics.height - anchors.left: parent.left - anchors.leftMargin: -margins.padding - TextMetrics { - id: pttTextMetrics - text: pttText.text - font: pttText.font - } - RalewayRegular { - id: pttText - color: hifi.colors.white; - width: parent.width; - wrapMode: (bar.currentIndex === 0) ? Text.NoWrap : Text.WordWrap; - font.italic: true - size: 16; + RalewayRegular { + id: pttText + x: 2 * margins.paddings; + color: hifi.colors.white; + anchors.bottom: parent.bottom; + width: rightMostInputLevelPos; + height: paintedHeight; + wrapMode: Text.WordWrap; + font.italic: true + size: 16; - text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to unmute.") : - qsTr("Press and hold grip triggers on both of your controllers to unmute."); - onTextChanged: { - if (pttTextMetrics.width > pttTextContainer.width) { - pttTextContainer.height = Math.ceil(pttTextMetrics.width / pttTextContainer.width) * pttTextMetrics.height; - } else { - pttTextContainer.height = pttTextMetrics.height; - } - } - } + text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to unmute.") : + qsTr("Press and hold grip triggers on both of your controllers to unmute."); } } - Separator {} + Separator { + id: pttEndSeparator; + } Item { From 4371723145a2c30d0e57ee2a3613d88fdb4a706a Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 11 Mar 2019 11:16:53 -0700 Subject: [PATCH 121/132] fix soft entity popping --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 03c50008a0..643e5afb70 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -181,9 +181,11 @@ void RenderableModelEntityItem::updateModelBounds() { updateRenderItems = true; } - if (model->getScaleToFitDimensions() != getScaledDimensions() || - model->getRegistrationPoint() != getRegistrationPoint() || - !model->getIsScaledToFit()) { + bool overridingModelTransform = model->isOverridingModelTransformAndOffset(); + if (!overridingModelTransform && + (model->getScaleToFitDimensions() != getScaledDimensions() || + model->getRegistrationPoint() != getRegistrationPoint() || + !model->getIsScaledToFit())) { // The machinery for updateModelBounds will give existing models the opportunity to fix their // translation/rotation/scale/registration. The first two are straightforward, but the latter two // have guards to make sure they don't happen after they've already been set. Here we reset those guards. From b7e1798d1bf9a50003c095ee70180b23279c5e91 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Mar 2019 11:28:30 -0700 Subject: [PATCH 122/132] better handling of unrigged vertices on skinned mesh --- libraries/fbx/src/FBXSerializer.cpp | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 5246242a1e..ca3659636f 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1043,7 +1043,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr cluster.transformLink = createMat4(values); } } - clusters.insert(getID(object.properties), cluster); + + // skip empty clusters + if (cluster.indices.size() > 0 && cluster.weights.size() > 0) { + clusters.insert(getID(object.properties), cluster); + } } else if (object.properties.last() == "BlendShapeChannel") { QByteArray name = object.properties.at(1).toByteArray(); @@ -1510,19 +1514,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr const HFMCluster& hfmCluster = extracted.mesh.clusters.at(i); int jointIndex = hfmCluster.jointIndex; HFMJoint& joint = hfmModel.joints[jointIndex]; - glm::mat4 transformJointToMesh = inverseModelTransform * joint.bindTransform; - glm::vec3 boneEnd = extractTranslation(transformJointToMesh); - glm::vec3 boneBegin = boneEnd; - glm::vec3 boneDirection; - float boneLength = 0.0f; - if (joint.parentIndex != -1) { - boneBegin = extractTranslation(inverseModelTransform * hfmModel.joints[joint.parentIndex].bindTransform); - boneDirection = boneEnd - boneBegin; - boneLength = glm::length(boneDirection); - if (boneLength > EPSILON) { - boneDirection /= boneLength; - } - } glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; ShapeVertices& points = shapeVertices.at(jointIndex); @@ -1575,16 +1566,19 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr int j = i * WEIGHTS_PER_VERTEX; // normalize weights into uint16_t - float totalWeight = weightAccumulators[j]; - for (int k = j + 1; k < j + WEIGHTS_PER_VERTEX; ++k) { + float totalWeight = 0.0f; + for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { totalWeight += weightAccumulators[k]; } + + const float ALMOST_HALF = 0.499f; if (totalWeight > 0.0f) { - const float ALMOST_HALF = 0.499f; float weightScalingFactor = (float)(UINT16_MAX) / totalWeight; for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) { extracted.mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF); } + } else { + extracted.mesh.clusterWeights[j] = (uint16_t)((float)(UINT16_MAX) + ALMOST_HALF); } } } else { From 2fb5e1ebc263efc96dedbe4fbf0ca7eba30f7c2d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 11 Mar 2019 12:36:29 -0700 Subject: [PATCH 123/132] quiet some logging --- scripts/system/miniTablet.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/system/miniTablet.js b/scripts/system/miniTablet.js index 449921514c..91c8b1edcf 100644 --- a/scripts/system/miniTablet.js +++ b/scripts/system/miniTablet.js @@ -1048,6 +1048,7 @@ // Track grabbed state and item. switch (message.action) { case "grab": + case "equip": grabbingHand = HAND_NAMES.indexOf(message.joint); grabbedItem = message.grabbedEntity; break; @@ -1056,7 +1057,7 @@ grabbedItem = null; break; default: - error("Unexpected grab message!"); + error("Unexpected grab message: " + JSON.stringify(message)); return; } @@ -1144,4 +1145,4 @@ setUp(); Script.scriptEnding.connect(tearDown); -}()); \ No newline at end of file +}()); From e515e9cc66d627c543e3e50d8983ffdb38936703 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 11 Mar 2019 12:38:54 -0700 Subject: [PATCH 124/132] fix cloneEntity function --- scripts/system/libraries/cloneEntityUtils.js | 15 ++++++--------- .../system/libraries/controllerDispatcherUtils.js | 4 +++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index e0f4aba84a..f789e19cd8 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -5,8 +5,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 entityIsCloneable:true, getGrabbableData:true, cloneEntity:true, propsAreCloneDynamic:true, Script, - propsAreCloneDynamic:true, Entities*/ +/* global entityIsCloneable:true, cloneEntity:true, propsAreCloneDynamic:true, Script, + propsAreCloneDynamic:true, Entities, Uuid */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -47,13 +47,10 @@ propsAreCloneDynamic = function(props) { }; cloneEntity = function(props) { - var entityToClone = props.id; - var props = Entities.getEntityProperties(entityToClone, ['certificateID', 'certificateType']) - var certificateID = props.certificateID; - // ensure entity is cloneable and does not have a certificate ID, whereas cloneable limits - // will now be handled by the server where the entity add will fail if limit reached - if (entityIsCloneable(props) && (!!certificateID || props.certificateType.indexOf('domainUnlimited') >= 0)) { - var cloneID = Entities.cloneEntity(entityToClone); + var entityIDToClone = props.id; + if (entityIsCloneable(props) && + (Uuid.isNull(props.certificateID) || props.certificateType.indexOf('domainUnlimited') >= 0)) { + var cloneID = Entities.cloneEntity(entityIDToClone); return cloneID; } return null; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 385ed954b0..5cb95f625d 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -156,7 +156,9 @@ DISPATCHER_PROPERTIES = [ "grab.equippableIndicatorOffset", "userData", "avatarEntity", - "owningAvatarID" + "owningAvatarID", + "certificateID", + "certificateType" ]; // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step From 07ddd4e1dd1d716d78c9c29db8b6d6ec880d7005 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 11 Mar 2019 14:27:47 -0700 Subject: [PATCH 125/132] moving key press detection to JSON --- .../resources/controllers/keyboardMouse.json | 1 + interface/src/Application.cpp | 15 ++------------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index 74c11203ef..9b3c711c63 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -5,6 +5,7 @@ { "from": "Keyboard.D", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.E", "when": "!Keyboard.Control", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.Q", "when": "!Keyboard.Control", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.T", "when": "!Keyboard.Control", "to": "Actions.TogglePushToTalk" }, { "comment" : "Mouse turn need to be small continuous increments", "from": { "makeAxis" : [ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3230419816..de4a6bb167 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1608,9 +1608,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo switch (actionEnum) { case Action::TOGGLE_PUSHTOTALK: if (state > 0.0f) { - audioScriptingInterface->setPushingToTalk(false); - } else if (state < 0.0f) { audioScriptingInterface->setPushingToTalk(true); + } else if (state <= 0.0f) { + audioScriptingInterface->setPushingToTalk(false); } break; @@ -4047,7 +4047,6 @@ void Application::keyPressEvent(QKeyEvent* event) { _keysPressed.insert(event->key(), *event); } - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isKeyCaptured(event) || isInterstitialMode()) { @@ -4215,10 +4214,6 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; - case Qt::Key_T: - audioScriptingInterface->setPushingToTalk(true); - break; - case Qt::Key_P: { if (!isShifted && !isMeta && !isOption && !event->isAutoRepeat()) { AudioInjectorOptions options; @@ -4325,12 +4320,6 @@ void Application::keyReleaseEvent(QKeyEvent* event) { _keyboardMouseDevice->keyReleaseEvent(event); } - auto audioScriptingInterface = reinterpret_cast(DependencyManager::get().data()); - switch (event->key()) { - case Qt::Key_T: - audioScriptingInterface->setPushingToTalk(false); - break; - } } void Application::focusOutEvent(QFocusEvent* event) { From b24b7fed3d96038aa5c55b2463534868935e1737 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Mar 2019 15:34:41 -0700 Subject: [PATCH 126/132] the root node isn't the first onegit add ../.git add ../. --- libraries/fbx/src/FBXSerializer.cpp | 12 ++++++------ libraries/render-utils/src/CauterizedModel.cpp | 8 ++++---- libraries/render-utils/src/Model.cpp | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index ca3659636f..52f4189bdb 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1486,8 +1486,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } - // if we don't have a skinned joint, parent to the model itself - if (extracted.mesh.clusters.isEmpty()) { + // the last cluster is the root cluster + { HFMCluster cluster; cluster.jointIndex = modelIDs.indexOf(modelID); if (cluster.jointIndex == -1) { @@ -1498,13 +1498,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } // whether we're skinned depends on how many clusters are attached - const HFMCluster& firstHFMCluster = extracted.mesh.clusters.at(0); - glm::mat4 inverseModelTransform = glm::inverse(modelTransform); if (clusterIDs.size() > 1) { // this is a multi-mesh joint const int WEIGHTS_PER_VERTEX = 4; int numClusterIndices = extracted.mesh.vertices.size() * WEIGHTS_PER_VERTEX; - extracted.mesh.clusterIndices.fill(0, numClusterIndices); + extracted.mesh.clusterIndices.fill(extracted.mesh.clusters.size() - 1, numClusterIndices); QVector weightAccumulators; weightAccumulators.fill(0.0f, numClusterIndices); @@ -1526,6 +1524,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr int newIndex = it.value(); // remember vertices with at least 1/4 weight + // FIXME: vertices with no weightpainting won't get recorded here const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; if (weight >= EXPANSION_WEIGHT_THRESHOLD) { // transform to joint-frame and save for later @@ -1582,7 +1581,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } } else { - // this is a single-mesh joint + // this is a single-joint mesh + const HFMCluster& firstHFMCluster = extracted.mesh.clusters.at(0); int jointIndex = firstHFMCluster.jointIndex; HFMJoint& joint = hfmModel.joints[jointIndex]; diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index cfb78d6bbc..cfdcec6e99 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -245,7 +245,7 @@ void CauterizedModel::updateRenderItems() { Transform renderTransform = modelTransform; if (useDualQuaternionSkinning) { - if (meshState.clusterDualQuaternions.size() == 1) { + if (meshState.clusterDualQuaternions.size() <= 2) { const auto& dq = meshState.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), dq.getScale(), @@ -253,7 +253,7 @@ void CauterizedModel::updateRenderItems() { renderTransform = modelTransform.worldTransform(transform); } } else { - if (meshState.clusterMatrices.size() == 1) { + if (meshState.clusterMatrices.size() <= 2) { renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); } } @@ -261,7 +261,7 @@ void CauterizedModel::updateRenderItems() { renderTransform = modelTransform; if (useDualQuaternionSkinning) { - if (cauterizedMeshState.clusterDualQuaternions.size() == 1) { + if (cauterizedMeshState.clusterDualQuaternions.size() <= 2) { const auto& dq = cauterizedMeshState.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), dq.getScale(), @@ -269,7 +269,7 @@ void CauterizedModel::updateRenderItems() { renderTransform = modelTransform.worldTransform(Transform(transform)); } } else { - if (cauterizedMeshState.clusterMatrices.size() == 1) { + if (cauterizedMeshState.clusterMatrices.size() <= 2) { renderTransform = modelTransform.worldTransform(Transform(cauterizedMeshState.clusterMatrices[0])); } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index a8d3e504f1..3c6565fca9 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -247,7 +247,7 @@ void Model::updateRenderItems() { Transform renderTransform = modelTransform; if (useDualQuaternionSkinning) { - if (meshState.clusterDualQuaternions.size() == 1) { + if (meshState.clusterDualQuaternions.size() <= 2) { const auto& dq = meshState.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), dq.getScale(), @@ -255,7 +255,7 @@ void Model::updateRenderItems() { renderTransform = modelTransform.worldTransform(Transform(transform)); } } else { - if (meshState.clusterMatrices.size() == 1) { + if (meshState.clusterMatrices.size() <= 2) { renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); } } From ca5ff3381b154d7466a6010e9b344e80cb94e407 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 11 Mar 2019 16:02:18 -0700 Subject: [PATCH 127/132] changing position of the mute warning setting --- interface/resources/qml/hifi/audio/Audio.qml | 54 ++++++++++---------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index a5138b3dd9..1a0457fd0a 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -125,22 +125,6 @@ Rectangle { } } - HifiControlsUit.Switch { - id: stereoInput; - height: root.switchHeight; - switchWidth: root.switchWidth; - labelTextOn: qsTr("Stereo input"); - backgroundOnColor: "#E3E3E3"; - checked: AudioScriptingInterface.isStereoInput; - onCheckedChanged: { - AudioScriptingInterface.isStereoInput = checked; - checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding - } - } - } - - ColumnLayout { - spacing: 24; HifiControlsUit.Switch { height: root.switchHeight; switchWidth: root.switchWidth; @@ -152,6 +136,23 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.noiseReduction; }); // restore binding } } + } + + ColumnLayout { + spacing: 24; + HifiControlsUit.Switch { + id: warnMutedSwitch + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Warn when muted"); + backgroundOnColor: "#E3E3E3"; + checked: AudioScriptingInterface.warnWhenMuted; + onClicked: { + AudioScriptingInterface.warnWhenMuted = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding + } + } + HifiControlsUit.Switch { id: audioLevelSwitch @@ -165,19 +166,20 @@ Rectangle { checked = Qt.binding(function() { return AvatarInputs.showAudioTools; }); // restore binding } } - } - RowLayout { - spacing: muteMic.spacing*2; - AudioControls.CheckBox { - spacing: muteMic.spacing - text: qsTr("Warn when muted"); - checked: AudioScriptingInterface.warnWhenMuted; - onClicked: { - AudioScriptingInterface.warnWhenMuted = checked; - checked = Qt.binding(function() { return AudioScriptingInterface.warnWhenMuted; }); // restore binding + HifiControlsUit.Switch { + id: stereoInput; + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Stereo input"); + backgroundOnColor: "#E3E3E3"; + checked: AudioScriptingInterface.isStereoInput; + onCheckedChanged: { + AudioScriptingInterface.isStereoInput = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding } } + } } From 80821e8b7e354ebd1d77a737b74c1e009e942538 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 11 Mar 2019 16:15:47 -0700 Subject: [PATCH 128/132] changing mic bar indicator when muted in PTT --- interface/resources/qml/hifi/audio/Audio.qml | 62 +++++++++---------- interface/resources/qml/hifi/audio/MicBar.qml | 6 +- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 5e849acedf..faa4f1de2f 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -140,6 +140,29 @@ Rectangle { checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding } } + + HifiControlsUit.Switch { + id: pttSwitch + height: root.switchHeight; + switchWidth: root.switchWidth; + labelTextOn: qsTr("Push To Talk (T)"); + backgroundOnColor: "#E3E3E3"; + checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; + onCheckedChanged: { + if (bar.currentIndex === 0) { + AudioScriptingInterface.pushToTalkDesktop = checked; + } else { + AudioScriptingInterface.pushToTalkHMD = checked; + } + checked = Qt.binding(function() { + if (bar.currentIndex === 0) { + return AudioScriptingInterface.pushToTalkDesktop; + } else { + return AudioScriptingInterface.pushToTalkHMD; + } + }); // restore binding + } + } } ColumnLayout { @@ -171,53 +194,26 @@ Rectangle { } } - Separator { id: pttStartSeparator; } Item { + anchors.left: parent.left width: rightMostInputLevelPos; - height: pttSwitch.height + pttText.height + 24; - HifiControlsUit.Switch { - id: pttSwitch - x: 2 * margins.paddings; - anchors.top: parent.top; - height: root.switchHeight; - switchWidth: root.switchWidth; - labelTextOn: qsTr("Push To Talk (T)"); - backgroundOnColor: "#E3E3E3"; - checked: (bar.currentIndex === 0) ? AudioScriptingInterface.pushToTalkDesktop : AudioScriptingInterface.pushToTalkHMD; - onCheckedChanged: { - if (bar.currentIndex === 0) { - AudioScriptingInterface.pushToTalkDesktop = checked; - } else { - AudioScriptingInterface.pushToTalkHMD = checked; - } - checked = Qt.binding(function() { - if (bar.currentIndex === 0) { - return AudioScriptingInterface.pushToTalkDesktop; - } else { - return AudioScriptingInterface.pushToTalkHMD; - } - }); // restore binding - } - } + height: pttText.height; RalewayRegular { id: pttText - x: 2 * margins.paddings; + x: margins.paddings; color: hifi.colors.white; - anchors.bottom: parent.bottom; width: rightMostInputLevelPos; height: paintedHeight; wrapMode: Text.WordWrap; font.italic: true size: 16; - text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to unmute.") : - qsTr("Press and hold grip triggers on both of your controllers to unmute."); + text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to talk.") : + qsTr("Press and hold grip triggers on both of your controllers to talk."); } } - Separator { - id: pttEndSeparator; - } + Separator { } Item { diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml index 491b9f9554..f51da9c381 100644 --- a/interface/resources/qml/hifi/audio/MicBar.qml +++ b/interface/resources/qml/hifi/audio/MicBar.qml @@ -159,7 +159,7 @@ Rectangle { color: parent.color; - text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? "MUTED PTT-(T)" : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); + text: (AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk) ? (HMD.active ? "MUTED PTT" : "MUTED PTT-(T)") : (AudioScriptingInterface.muted ? "MUTED" : "MUTE"); font.pointSize: 12; } @@ -169,7 +169,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; color: parent.color; } @@ -180,7 +180,7 @@ Rectangle { verticalCenter: parent.verticalCenter; } - width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? 25 : 50; + width: AudioScriptingInterface.pushToTalk && !AudioScriptingInterface.pushingToTalk ? (HMD.active ? 27 : 25) : 50; height: 4; color: parent.color; } From 3c5fd069595dec2595dd74fbf4e78dee30f5a0a7 Mon Sep 17 00:00:00 2001 From: Jason Najera <39922250+r3tk0n@users.noreply.github.com> Date: Mon, 11 Mar 2019 16:16:58 -0700 Subject: [PATCH 129/132] Remove useless comment. Comment was not helpful. --- interface/src/scripting/Audio.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index c4dfcffb61..6b4ad80231 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -40,7 +40,6 @@ Audio::Audio() : _devices(_contextIsHMD) { connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); - // when pushing to talk changed, handle it. connect(this, &Audio::pushingToTalkChanged, this, &Audio::handlePushedToTalk); enableNoiseReduction(enableNoiseReductionSetting.get()); onContextChanged(); From 84b177996b346deaf82712c4ff74f0ebfa8c608b Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 11 Mar 2019 16:43:26 -0700 Subject: [PATCH 130/132] removing dead code --- .../controllers/controllerModules/pushToTalk.js | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/scripts/system/controllers/controllerModules/pushToTalk.js b/scripts/system/controllers/controllerModules/pushToTalk.js index 6b1bacc367..11335ba2f5 100644 --- a/scripts/system/controllers/controllerModules/pushToTalk.js +++ b/scripts/system/controllers/controllerModules/pushToTalk.js @@ -11,17 +11,10 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); -(function () { // BEGIN LOCAL_SCOPE +(function() { // BEGIN LOCAL_SCOPE function PushToTalkHandler() { var _this = this; this.active = false; - //var pttMapping, mappingName; - - this.setup = function () { - //mappingName = 'Hifi-PTT-Dev-' + Math.random(); - //pttMapping = Controller.newMapping(mappingName); - //pttMapping.enable(); - }; this.shouldTalk = function (controllerData) { // Set up test against controllerData here... @@ -53,10 +46,6 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(true, [], []); }; - this.cleanup = function () { - //pttMapping.disable(); - }; - this.parameters = makeDispatcherModuleParameters( 950, ["head"], @@ -68,9 +57,8 @@ Script.include("/~/system/libraries/controllers.js"); enableDispatcherModule("PushToTalk", pushToTalk); function cleanup() { - pushToTalk.cleanup(); disableDispatcherModule("PushToTalk"); }; Script.scriptEnding.connect(cleanup); -}()); // END LOCAL_SCOPE +}()); // END LOCAL_SCOPE From 2b32b77bed0875cb72c98948f82fcc7aa2513b37 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 11 Mar 2019 17:32:40 -0700 Subject: [PATCH 131/132] handle case when clusterMatrices.size() == 0 --- .vs/slnx.sqlite | Bin 0 -> 77824 bytes libraries/render-utils/src/CauterizedModel.cpp | 8 ++++---- libraries/render-utils/src/Model.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 .vs/slnx.sqlite diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..4227190a376249e4edb90c9be12165dbacd46945 GIT binary patch literal 77824 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 Date: Mon, 11 Mar 2019 17:59:48 -0700 Subject: [PATCH 132/132] fix joint out not being renamed with jointRotationOffset2 --- .../src/model-baker/PrepareJointsTask.cpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index b8bcdb386e..a746b76c1f 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -101,23 +101,24 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu } if (newJointRot) { - for (const auto& jointIn : jointsIn) { + for (auto& jointOut : jointsOut) { - auto jointNameMapKey = jointNameMapping.key(jointIn.name); - int mappedIndex = jointIndices.value(jointIn.name); + auto jointNameMapKey = jointNameMapping.key(jointOut.name); + int mappedIndex = jointIndices.value(jointOut.name); if (jointNameMapping.contains(jointNameMapKey)) { - // delete and replace with hifi name - jointIndices.remove(jointIn.name); - jointIndices.insert(jointNameMapKey, mappedIndex); + jointIndices.remove(jointOut.name); + jointOut.name = jointNameMapKey; + jointIndices.insert(jointOut.name, mappedIndex); } else { // nothing mapped to this fbx joint name - if (jointNameMapping.contains(jointIn.name)) { + if (jointNameMapping.contains(jointOut.name)) { // but the name is in the list of hifi names is mapped to a different joint - int extraIndex = jointIndices.value(jointIn.name); - jointIndices.remove(jointIn.name); - jointIndices.insert("", extraIndex); + int extraIndex = jointIndices.value(jointOut.name); + jointIndices.remove(jointOut.name); + jointOut.name = ""; + jointIndices.insert(jointOut.name, extraIndex); } } }