From a63393bc55cf1d68deff6dfceda6d744a9cba214 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Feb 2018 13:32:30 -0800 Subject: [PATCH 01/80] undo part of #12312 --- interface/src/ui/overlays/Web3DOverlay.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 5fb4d58340..4f96e70aa9 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -80,7 +80,6 @@ Web3DOverlay::Web3DOverlay() { _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED _webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); - } Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : @@ -201,6 +200,11 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("offscreenFlags", flags); _webSurface->getSurfaceContext()->setContextProperty("AddressManager", DependencyManager::get().data()); + + _webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + _webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED + _webSurface->getSurfaceContext()->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance()); + // in Qt 5.10.0 there is already an "Audio" object in the QML context // though I failed to find it (from QtMultimedia??). So.. let it be "AudioScriptingInterface" _webSurface->getSurfaceContext()->setContextProperty("AudioScriptingInterface", DependencyManager::get().data()); From 3a7e626463de5401babf9f5286f36c8463a014c6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Feb 2018 10:19:30 -0800 Subject: [PATCH 02/80] Merge pull request #12316 from zfox23/commerce_inspectionCertRedesign Commerce: Inspection Certificate Redesign --- .../InspectionCertificate.qml | 548 ++++++++++++------ .../images/cert-bg-gold-split.png | Bin 0 -> 189708 bytes .../inspectionCertificate/images/cert-bg.jpg | Bin 64011 -> 0 bytes .../images/nocert-bg-split.png | Bin 0 -> 17201 bytes .../commerce/wallet/sendMoney/SendMoney.qml | 2 +- .../wallet/sendMoney/images/loader.gif | Bin 59412 -> 0 bytes 6 files changed, 362 insertions(+), 188 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg-gold-split.png delete mode 100644 interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg create mode 100644 interface/resources/qml/hifi/commerce/inspectionCertificate/images/nocert-bg-split.png delete mode 100644 interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 421fa4b074..2c7319be09 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -19,21 +19,30 @@ import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../wallet" as HifiWallet -// references XXX from root context - Rectangle { HifiConstants { id: hifi; } id: root; - property string marketplaceUrl; - property string certificateId; + property string marketplaceUrl: ""; + property string certificateId: ""; property string itemName: "--"; property string itemOwner: "--"; property string itemEdition: "--"; property string dateOfPurchase: "--"; + property string itemCost: "--"; + property string certTitleTextColor: hifi.colors.darkGray; + property string certTextColor: hifi.colors.white; + property string infoTextColor: hifi.colors.blueAccent; + // 0 means replace none + // 4 means replace all but "Item Edition" + // 5 means replace all 5 replaceable fields + property int certInfoReplaceMode: 5; property bool isLightbox: false; property bool isMyCert: false; - property bool isCertificateInvalid: false; + property bool useGoldCert: true; + property bool certificateInfoPending: true; + property int certificateStatus: 0; + property bool certificateStatusPending: true; // Style color: hifi.colors.faintGray; Connections { @@ -45,71 +54,130 @@ Rectangle { } else { root.marketplaceUrl = result.data.marketplace_item_url; root.isMyCert = result.isMyCert ? result.isMyCert : false; - root.itemOwner = root.isCertificateInvalid ? "--" : (root.isMyCert ? Account.username : - "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"); - root.itemEdition = root.isCertificateInvalid ? "Uncertified Copy" : - (result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run)); - root.dateOfPurchase = root.isCertificateInvalid ? "" : getFormattedDate(result.data.transfer_created_at * 1000); - root.itemName = result.data.marketplace_item_name; + + if (root.certInfoReplaceMode > 3) { + root.itemName = result.data.marketplace_item_name; + // "\u2022" is the Unicode character 'BULLET' - it's what's used in password fields on the web, etc + root.itemOwner = root.isMyCert ? Account.username : + "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; + root.dateOfPurchase = root.isMyCert ? getFormattedDate(result.data.transfer_created_at * 1000) : "Undisclosed"; + root.itemCost = (root.isMyCert && result.data.cost !== undefined) ? result.data.cost : "Undisclosed"; + } + if (root.certInfoReplaceMode > 4) { + root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); + } + + if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + if (root.isMyCert) { + errorText.text = "This item is an uncertified copy of an item you purchased."; + } else { + errorText.text = "The person who placed this item doesn't own it."; + } + } if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") { - titleBarText.text = "Invalid Certificate"; - titleBarText.color = hifi.colors.redHighlight; + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Certificate\nNo Longer Valid"; popText.text = ""; + showInMarketplaceButton.visible = false; + // "Edition" text previously set above in this function + // "Owner" text previously set above in this function + // "Purchase Date" text previously set above in this function + // "Purchase Price" text previously set above in this function if (result.data.invalid_reason) { errorText.text = result.data.invalid_reason; } } else if (result.data.transfer_status[0] === "pending") { + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; titleBarText.text = "Certificate Pending"; + popText.text = ""; + showInMarketplaceButton.visible = true; + // "Edition" text previously set above in this function + // "Owner" text previously set above in this function + // "Purchase Date" text previously set above in this function + // "Purchase Price" text previously set above in this function errorText.text = "The status of this item is still pending confirmation. If the purchase is not confirmed, " + "this entity will be cleaned up by the domain."; - errorText.color = hifi.colors.baseGray; } } + root.certificateInfoPending = false; } onUpdateCertificateStatus: { - if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS - // NOP - } else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT - root.isCertificateInvalid = true; - errorText.text = "Verification of this certificate timed out."; - errorText.color = hifi.colors.redHighlight; - } else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED - root.isCertificateInvalid = true; - titleBarText.text = "Invalid Certificate"; - titleBarText.color = hifi.colors.redHighlight; - + root.certificateStatus = certStatus; + if (root.certificateStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS + root.useGoldCert = true; + root.certTitleTextColor = hifi.colors.darkGray; + root.certTextColor = hifi.colors.white; + root.infoTextColor = hifi.colors.blueAccent; + titleBarText.text = "Certificate"; + popText.text = "PROOF OF PROVENANCE"; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 5; + // "Item Name" text will be set in "onCertificateInfoResult()" + // "Edition" text will be set in "onCertificateInfoResult()" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + errorText.text = ""; + } else if (root.certificateStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Request Timed Out"; popText.text = ""; + showInMarketplaceButton.visible = false; + root.certInfoReplaceMode = 0; + root.itemName = ""; + root.itemEdition = ""; root.itemOwner = ""; - dateOfPurchaseHeader.text = ""; root.dateOfPurchase = ""; - root.itemEdition = "Uncertified Copy"; - + root.itemCost = ""; + errorText.text = "Your request to inspect this item timed out. Please try again later."; + } else if (root.certificateStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Certificate\nNo Longer Valid"; + popText.text = ""; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 5; + // "Item Name" text will be set in "onCertificateInfoResult()" + // "Edition" text will be set in "onCertificateInfoResult()" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item."; - errorText.color = hifi.colors.baseGray; - } else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED - root.isCertificateInvalid = true; + } else if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; titleBarText.text = "Invalid Certificate"; - titleBarText.color = hifi.colors.redHighlight; - popText.text = ""; - root.itemOwner = ""; - dateOfPurchaseHeader.text = ""; - root.dateOfPurchase = ""; - root.itemEdition = "Uncertified Copy"; - - errorText.text = "The avatar who rezzed this item doesn't own it."; - errorText.color = hifi.colors.baseGray; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 4; + // "Item Name" text will be set in "onCertificateInfoResult()" + root.itemEdition = "Uncertified Copy" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + // "Error Text" text will be set in "onCertificateInfoResult()" } else { console.log("Unknown certificate status received from ledger signal!"); } - } - } - - onCertificateIdChanged: { - if (certificateId !== "") { - Commerce.certificateInfo(certificateId); + + root.certificateStatusPending = false; + // We've gotten cert status - we are GO on getting the cert info + Commerce.certificateInfo(root.certificateId); } } @@ -122,9 +190,35 @@ Rectangle { hoverEnabled: true; } - Image { + Rectangle { + id: loadingOverlay; + z: 998; + + visible: root.certificateInfoPending || root.certificateStatusPending; anchors.fill: parent; - source: "images/cert-bg.jpg"; + color: Qt.rgba(0.0, 0.0, 0.0, 0.7); + + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + } + + AnimatedImage { + source: "../common/images/loader.gif" + width: 96; + height: width; + anchors.verticalCenter: parent.verticalCenter; + anchors.horizontalCenter: parent.horizontalCenter; + } + } + + Image { + id: backgroundImage; + anchors.fill: parent; + source: root.useGoldCert ? "images/cert-bg-gold-split.png" : "images/nocert-bg-split.png"; } // Title text @@ -137,16 +231,17 @@ Rectangle { anchors.top: parent.top; anchors.topMargin: 40; anchors.left: parent.left; - anchors.leftMargin: 45; + anchors.leftMargin: 36; anchors.right: parent.right; + anchors.rightMargin: 8; height: paintedHeight; // Style - color: hifi.colors.darkGray; + color: root.certTitleTextColor; + wrapMode: Text.WordWrap; } // Title text RalewayRegular { id: popText; - text: "Proof of Provenance"; // Text size size: 16; // Anchors @@ -154,9 +249,38 @@ Rectangle { anchors.topMargin: 4; anchors.left: titleBarText.left; anchors.right: titleBarText.right; - height: paintedHeight; + height: text === "" ? 0 : paintedHeight; // Style - color: hifi.colors.darkGray; + color: root.certTitleTextColor; + } + + // "Close" button + HiFiGlyphs { + id: closeGlyphButton; + text: hifi.glyphs.close; + color: hifi.colors.white; + size: 26; + anchors.top: parent.top; + anchors.topMargin: 10; + anchors.right: parent.right; + anchors.rightMargin: 10; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + onExited: { + parent.text = hifi.glyphs.close; + } + onClicked: { + if (root.isLightbox) { + root.visible = false; + } else { + sendToScript({method: 'inspectionCertificate_closeClicked', closeGoesToPurchases: root.closeGoesToPurchases}); + } + } + } } // @@ -164,11 +288,13 @@ Rectangle { // Item { id: certificateContainer; - anchors.top: popText.bottom; - anchors.topMargin: 30; - anchors.bottom: buttonsContainer.top; + anchors.top: titleBarText.top; + anchors.topMargin: 110; + anchors.bottom: infoContainer.top; anchors.left: parent.left; + anchors.leftMargin: titleBarText.anchors.leftMargin; anchors.right: parent.right; + anchors.rightMargin: 24; RalewayRegular { id: itemNameHeader; @@ -178,9 +304,7 @@ Rectangle { // Anchors anchors.top: parent.top; anchors.left: parent.left; - anchors.leftMargin: 45; anchors.right: parent.right; - anchors.rightMargin: 16; height: paintedHeight; // Style color: hifi.colors.darkGray; @@ -197,79 +321,30 @@ Rectangle { anchors.right: itemNameHeader.right; height: paintedHeight; // Style - color: hifi.colors.white; + color: root.certTextColor; elide: Text.ElideRight; MouseArea { + enabled: showInMarketplaceButton.visible; anchors.fill: parent; hoverEnabled: enabled; onClicked: { sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); } onEntered: itemName.color = hifi.colors.blueHighlight; - onExited: itemName.color = hifi.colors.white; + onExited: itemName.color = root.certTextColor; } } - RalewayRegular { - id: ownedByHeader; - text: "OWNER"; - // Text size - size: 16; - // Anchors - anchors.top: itemName.bottom; - anchors.topMargin: 28; - anchors.left: parent.left; - anchors.leftMargin: 45; - anchors.right: parent.right; - anchors.rightMargin: 16; - height: paintedHeight; - // Style - color: hifi.colors.darkGray; - } - RalewayRegular { - id: ownedBy; - text: root.itemOwner; - // Text size - size: 22; - // Anchors - anchors.top: ownedByHeader.bottom; - anchors.topMargin: 8; - anchors.left: ownedByHeader.left; - height: paintedHeight; - // Style - color: hifi.colors.white; - elide: Text.ElideRight; - } - AnonymousProRegular { - id: isMyCertText; - visible: root.isMyCert && !root.isCertificateInvalid; - text: "(Private)"; - size: 18; - // Anchors - anchors.top: ownedBy.top; - anchors.topMargin: 4; - anchors.bottom: ownedBy.bottom; - anchors.left: ownedBy.right; - anchors.leftMargin: 6; - anchors.right: ownedByHeader.right; - // Style - color: hifi.colors.white; - elide: Text.ElideRight; - verticalAlignment: Text.AlignVCenter; - } - RalewayRegular { id: editionHeader; text: "EDITION"; // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; + anchors.top: itemName.bottom; anchors.topMargin: 28; anchors.left: parent.left; - anchors.leftMargin: 45; anchors.right: parent.right; - anchors.rightMargin: 16; height: paintedHeight; // Style color: hifi.colors.darkGray; @@ -286,21 +361,117 @@ Rectangle { anchors.right: editionHeader.right; height: paintedHeight; // Style - color: hifi.colors.white; + color: root.certTextColor; + } + + // "Show In Marketplace" button + HifiControlsUit.Button { + id: showInMarketplaceButton; + enabled: root.marketplaceUrl; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.light; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 48; + anchors.right: parent.right; + width: 200; + height: 40; + text: "View In Market" + onClicked: { + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); + } + } + } + // + // "CERTIFICATE" END + // + + // + // "INFO CONTAINER" START + // + Item { + id: infoContainer; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.leftMargin: titleBarText.anchors.leftMargin; + anchors.right: parent.right; + anchors.rightMargin: 24; + height: root.useGoldCert ? 220 : 372; + + RalewayRegular { + id: errorText; + visible: !root.useGoldCert; + // Text size + size: 20; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 36; + anchors.left: parent.left; + anchors.right: parent.right; + height: 116; + // Style + wrapMode: Text.WordWrap; + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignTop; + } + + RalewayRegular { + id: ownedByHeader; + text: "OWNER"; + // Text size + size: 16; + // Anchors + anchors.top: errorText.visible ? errorText.bottom : parent.top; + anchors.topMargin: 28; + anchors.left: parent.left; + anchors.right: parent.right; + height: paintedHeight; + // Style + color: hifi.colors.darkGray; + } + + RalewayRegular { + id: ownedBy; + text: root.itemOwner; + // Text size + size: 22; + // Anchors + anchors.top: ownedByHeader.bottom; + anchors.topMargin: 8; + anchors.left: ownedByHeader.left; + height: paintedHeight; + // Style + color: root.infoTextColor; + elide: Text.ElideRight; + } + AnonymousProRegular { + id: isMyCertText; + visible: root.isMyCert && ownedBy.text !== "--" && ownedBy.text !== ""; + text: "(Private)"; + size: 18; + // Anchors + anchors.top: ownedBy.top; + anchors.topMargin: 4; + anchors.bottom: ownedBy.bottom; + anchors.left: ownedBy.right; + anchors.leftMargin: 6; + anchors.right: ownedByHeader.right; + // Style + color: root.infoTextColor; + elide: Text.ElideRight; + verticalAlignment: Text.AlignVCenter; } RalewayRegular { id: dateOfPurchaseHeader; - text: "DATE OF PURCHASE"; + text: "PURCHASE DATE"; // Text size size: 16; // Anchors - anchors.top: edition.bottom; + anchors.top: ownedBy.bottom; anchors.topMargin: 28; anchors.left: parent.left; - anchors.leftMargin: 45; - anchors.right: parent.right; - anchors.rightMargin: 16; + anchors.right: parent.horizontalCenter; + anchors.rightMargin: 8; height: paintedHeight; // Style color: hifi.colors.darkGray; @@ -317,73 +488,58 @@ Rectangle { anchors.right: dateOfPurchaseHeader.right; height: paintedHeight; // Style - color: hifi.colors.white; + color: root.infoTextColor; } RalewayRegular { - id: errorText; + id: priceHeader; + text: "PURCHASE PRICE"; // Text size - size: 20; + size: 16; // Anchors - anchors.top: dateOfPurchase.bottom; - anchors.topMargin: 36; - anchors.left: dateOfPurchase.left; - anchors.right: dateOfPurchase.right; - anchors.bottom: parent.bottom; - // Style - wrapMode: Text.WordWrap; - color: hifi.colors.redHighlight; - verticalAlignment: Text.AlignTop; - } - } - // - // "CERTIFICATE" END - // - - Item { - id: buttonsContainer; - anchors.bottom: parent.bottom; - anchors.bottomMargin: 30; - anchors.left: parent.left; - anchors.right: parent.right; - height: 50; - - // "Cancel" button - HifiControlsUit.Button { - color: hifi.buttons.noneBorderlessWhite; - colorScheme: hifi.colorSchemes.light; - anchors.top: parent.top; - anchors.left: parent.left; - anchors.leftMargin: 30; - width: parent.width/2 - 50; - height: 50; - text: "close"; - onClicked: { - if (root.isLightbox) { - root.visible = false; - } else { - sendToScript({method: 'inspectionCertificate_closeClicked', closeGoesToPurchases: root.closeGoesToPurchases}); - } - } - } - - // "Show In Marketplace" button - HifiControlsUit.Button { - id: showInMarketplaceButton; - enabled: root.marketplaceUrl; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.light; - anchors.top: parent.top; + anchors.top: ownedBy.bottom; + anchors.topMargin: 28; + anchors.left: parent.horizontalCenter; anchors.right: parent.right; - anchors.rightMargin: 30; - width: parent.width/2 - 50; - height: 50; - text: "View In Market" - onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); - } + height: paintedHeight; + // Style + color: hifi.colors.darkGray; + } + HiFiGlyphs { + id: hfcGlyph; + visible: priceText.text !== "Undisclosed" && priceText.text !== ""; + text: hifi.glyphs.hfc; + // Size + size: 24; + // Anchors + anchors.top: priceHeader.bottom; + anchors.topMargin: 8; + anchors.left: priceHeader.left; + width: visible ? paintedWidth + 6 : 0; + height: 40; + // Style + color: root.infoTextColor; + verticalAlignment: Text.AlignTop; + horizontalAlignment: Text.AlignLeft; + } + AnonymousProRegular { + id: priceText; + text: root.itemCost; + // Text size + size: 18; + // Anchors + anchors.top: priceHeader.bottom; + anchors.topMargin: 8; + anchors.left: hfcGlyph.right; + anchors.right: priceHeader.right; + height: paintedHeight; + // Style + color: root.infoTextColor; } } + // + // "INFO CONTAINER" END + // // // FUNCTION DEFINITIONS START @@ -404,19 +560,11 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'inspectionCertificate_setCertificateId': + resetCert(false); root.certificateId = message.certificateId; break; case 'inspectionCertificate_resetCert': - titleBarText.text = "Certificate"; - popText.text = "PROOF OF PURCHASE"; - root.certificateId = ""; - root.itemName = "--"; - root.itemOwner = "--"; - root.itemEdition = "--"; - root.dateOfPurchase = "--"; - root.marketplaceUrl = ""; - root.isMyCert = false; - errorText.text = ""; + resetCert(true); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); @@ -424,7 +572,33 @@ Rectangle { } signal sendToScript(var message); + function resetCert(alsoResetCertID) { + if (alsoResetCertID) { + root.certificateId = ""; + } + root.certInfoReplaceMode = 5; + root.certificateInfoPending = true; + root.certificateStatusPending = true; + root.useGoldCert = true; + root.certTitleTextColor = hifi.colors.darkGray; + root.certTextColor = hifi.colors.white; + root.infoTextColor = hifi.colors.blueAccent; + titleBarText.text = "Certificate"; + popText.text = ""; + root.itemName = "--"; + root.itemOwner = "--"; + root.itemEdition = "--"; + root.dateOfPurchase = "--"; + root.marketplaceUrl = ""; + root.itemCost = "--"; + root.isMyCert = false; + errorText.text = ""; + } + function getFormattedDate(timestamp) { + if (timestamp === "--") { + return "--"; + } function addLeadingZero(n) { return n < 10 ? '0' + n : '' + n; } @@ -449,7 +623,7 @@ Rectangle { var min = addLeadingZero(a.getMinutes()); var sec = addLeadingZero(a.getSeconds()); - return year + '-' + month + '-' + day + '
' + drawnHour + ':' + min + amOrPm; + return year + '-' + month + '-' + day + ' ' + drawnHour + ':' + min + amOrPm; } // // FUNCTION DEFINITIONS END diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg-gold-split.png b/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg-gold-split.png new file mode 100644 index 0000000000000000000000000000000000000000..14a17df0b16f35320b67561440d6d9cf11cb2f29 GIT binary patch literal 189708 zcmaHSQ*R6WUZzJ)RLFuF?FAwtO=B}nL)=ofc zM+c&R7>!LF-GKZgB>zgVcaoR?-^32C|J^84M|(z3V<$#t1|~*(`+s!(N3bhU#r*%2 z@jphps(Cq?Gpd-oI=Z=-nyZ*wkp2tyw0HXdC;BJQNnV~u(Z$-*+QHaP%+b`%-rNBw zEyhpcY45~fW^Km9#%yfD%*D(`&tc4FO3%j1%1&>>X39j*%EoQR!OhKK#$;mhFVFvk zXJ=*+=MWZUmSAIMV`diT&JyLltadV&Wof;@skFA`<^7;1XqK7UAY(=4O%*78mAXBl&Nv+5gnN z|H2CYKe3DgM0||@tjYhiHvhU*#3cTC{_i6YAo@RNWbRIVHQxF*>24ga1nel+<8cgFdI zQ2V|16@IYNmailbeMR}mDdUU$#Q6F6misB2-f{ny=o$mqamYN%Vq{6A2;t~2F<50(mmvBOReJ;`dqIKp9hj3Ja z`*;;E#_=vD0J68$KE;l++J6o4=LN&U$-zZ4^GHgg{R7wAr_3-WDx_Hi?m0MM&2k+vCJ<1yk&LHVC%nlQ`{QPVL9)ldp9;7>)NX`1M z9RZ(|?zsvcTbob4EQFRSjw3z&S10f6ajZ2fzYMS!uV)p&jP#^7e7XVmZyDSUAR7`u z$*+~8miN#6Z(Zt)fcK2=HvSf8>4UCw%`3>a{*SMj??)HE&*y;8@4N&bZiAz%Ad>i# zK=HqwdVj7g@AU|tN3R~AH`)aKKi+%!@)HjB2AvU`RVx)O`1@4$_vKbHt^+>m1ix&CaO;P{R_zv8| z$IZMpJ~LoCRD>50YQsTTS;ZuB5Ao8=WWA799{Xp#;5jo>ClB99? zia4VCsYF2I#&yuo9{7il~t_O4WZd~*bsVlY_D1W(px_A zrQaRT4>&UJumMpMBb$SKN8cDCh)FeOA8EcwV2DS^l}9Ko7=7*hj$}H6@^N7!m!pR( zHsAjh;g8aOjX>{FVK~7_{(940=uhai@`PC+RGTP3`K~Q|4RsV@_X93_sJWTc5TyCL zsfKE|*vB@)xFU(nI){OA;poZtV=-=J2=aBE zFpFWuEjKG4%W&IWhOT7tphGjHYu{hIMA?q`f8vbcl#A=`tk z__}$t;?i&}KDb{55p1Vsi(iJM7^qu|72^*#cCLXll<*B#AlY#2Vxg7$qQf~#_fd_A zE}fGYmp57JO=|izzaI&SvN^0HmrSf5qa5u)YwrQ6&jYW_512g&_lyUGa&4cQ5Wu8< zoA@Z0BKBsOqKkE_Z~I7=y`{*?1Q& z4kYek(j!yawetfY8LI7e9~EWNq*(Jtb3UJ{JlrYRRRd-tsQ78yFo;2K-FN%uGT`%f zuPt<3iO=`1Dya?V^Erm^{L+i7jr-dX@>bAbv;qofs=JTzxsm(GNpqOl`*hwi@e=(T zomofYUj#q%-tSIax~K2EokpBLymWQt6SVl0{yx8}!EKF2*y7KrJm{d?^ei&C#&9)r z2CAgKO-RJ}>Ik*2DBn+-&K(k!OR(GZBP1ObEX7Fn%T_2q+C6ZWsVTR}NxWNCSJ4ft zv^;^%%4~WMs2FLnc!{hIQRzE6tXNLY-ybs!yyM-kE+hnk4D!pS{s>OcENIgN69-w&sep#a%OQc>LZaICv z0kVipSg4U+^E78+Eu;x1mmGUL&B)(eo&IN&c;AVzrK;^ z#Xi1KB^%xOUoYs)%1fNTt~?XzZn&4rBK-I`H)2=_j@%PxTv^$kgo&)P1{^YtHqL@Q z5L|sCYHFpJ_m5e~T#ewf5$RqlxlQH?-d-p%lJV>S)nIEzgoJVv=4UFGCopq90cz{8 zP1*$hmlCpU(&k_kWqvY3TgfT{8z!TP2==l@vWU@66h*P!$0vxR=tfZ@m&HBh@l_2R z@BDL{hgsx#8Ir0Wv*K>wQXYHCiQdfgZVoed8ac0CEx^CfKA|;!%D)`zu}Zp!<_%BX zPiyoGXAra(Y-Gc5t%eX;Or*UuWaA5dplP`*gHGA@#%W_X7R+wl1f23l|U z^AlhWLZgPW$)HdBkRglUx^OOS-{xj@aE6TBV$7l0LEsaMkfX`gMJch$j*C0?@A?dx z&Vewe%BtqqoKjN~NWRydf`9(ZkKv5L$P|~cT}grP$b?%H^Y$_M)WA`=k;`b~VH~X1 zq1$q!EWrn$g#S(I*SPX5YXrqGkQKkNis3963pPn(f}dnT)|M`zhPxOmjLCiHb?PGz z)a;NF#xH^U5+7(Sxm`XEJ2|O7nG42(ST(Pxfe5JspDCBE7m_SE8jz%m>%=MX9#kfA z3npF_tD3f-A=_Q@Ew0MnGQd? z)$_aiqZNGFE2`$PMpUHZJj6+e;LFAlW4%#hbNN(2(5`=4T_tDlO-3Qu#qPU(+d=Qw zy=tA);b!YVVb90H!B@^xSr{l`o2{aaWD^8_d{rLKo=$K5 z2K<*WZ;?v#>?eC#zRHKsfEE0oV5F#pNf}@sIZ3P+K#LDIZ zG3GgL3q{S!B+#qt6XN2dbz*!nHMDY3%nYKyu$O+$E3PIuyhPmKCgmW9EmCnK-^<_0 zVrALPfIhV!2uw^MtcK|C`kEu3tp5rXbj4J{t)iMVUaeP5B@+tR3P?O`F!HW)k%b2n@GyZ+JaLrlv z74mL^L74YgYY`O+Wz}P#d5H(`pjAUkve72;GWa*~R^jReOWGq73jE z6;_n&90Z763n3hUfUXx0jp}8q->Ah>dHAEiUIynbs0gr)Y8 zn}H=L=$$EnyT#;$G2_((S)*v!ob8+#^*fj-RvnZPCj~9`tE-sV&Yn}Kkk(pf3<&}e zZYacWRv}UH^zM zeX8F_Ov9Z9ab?@;l^8>Uq@f{Zbx1;)kgZ{_>6O~62$|34zq?7LTXap}+N=ppb6(V0 z)9)6M`lZicWBPi`0HM;>Sl=&v1)UHhOeR&=O&2G(Yd9MPK!LS{wO~NvjzK-XaKDA7tLq+CnHIE%mtHI$q-Rad6#veH=@(VHIy4e!va zZA-(|JQhw%fQzn1 zw0A}`I&A;Dd_H(JN%VBJ0?tck9`N4PS9!uTk}5st8r$*#8%%yJMIal zIwVw^CS%HsauUmRKy&n& zh|yX4LKj61+V**jY@-Xev~y(-*vp@~(-UZ-dn}_N1^@Kwm9kNpcroKaS*M zqiKeN*0-zY4<$KRw=g__=tblmMZtTzrZcZ`l`GpzR;RA5MO5pA`Vq^9Dz{=m)M!_e z-m!bjAphR9WK@2`okCX*n3cAqz7a9GQ+n~}7FBJFvki-+9h{~dmU*Ucm~S1`J9AK4 z1AmGUpG|HE9$2A>G?&nl_hJg}j_N4iPvP&sDTMH+ef)QkjHNT5 z8B$H>N#qUFJA1a!9x%xaw`rvN0HrT@`O%0BrkT}{c$rv20z#m3<$EOSr?3gFWwBo+ z@JJE>Y}o5%q-!ffi{b%)*mFBqh$hri>4o7XxOwya&g#iWV&){OuIMRRU7;oP5FpgG z`y(|oZhOwB$_clnL!jBG0}v!llo@FkthyY{@5fzrf`b$3D6>8<~?t~Cu5EJ(U)twPX}Hi z%GtxZaT4PqL2kUl5FR`kncO>+6ZU|owUH=^JaXH$4J@)G)sQ5h&fc1uv#aOHUbdwR00$CXh~{M1c%4SQC3=^%+6HaUxY=LYS&# zVqr4SUB9u;n@wyG9SdI@%kN{h3+13iF%uD5MX}?aO#iFM{)a??P(;w3#F_V@qExVu zIJm{>?-;-uHVi=grEIB^VejV2^jbn237F+~+sTZU{$X*PAU zp4h9|(f%4u2zd9nR8I84*~Hs4vK1-S<5*)&{7|WN#`I0}MzNQ!z>cqwIA>W(TnFim z!I6h1l7@&36=$Qd+Gk?u8b-M)vLqm+9|JFNS*bhUYBu2 zD&H>4%XN#%yz9+zQxs-z-AzV9;SDMxHk{o4GwwRvgw-G31%93i-7fDZ`DpNRa#rbv z{lN%6z(&o4V~~84;A9m)1fSN{BrkvFx#q{poXYHk&oCcQ#n^9@A>-nx!)#VCc5}bq zU#m(Q$y(d7c9MB2!H1&plp;$K6?e<^9L@?!S%0w7iqb-lkewt3FBI8D`k}rc@w6C; zOf`b**Kyvi=STJUuq$-`TVBXSZuqks4U)z_etUz9eH6+rF*3J^XCaJCPTJ=ORcKyp zKA<%^umkiSPW%1@OSDL-5>(#s$egY*jv37`P*5QcY(bRj#YsB|qgHzkT3VH6Zcg`W z0}jM0NHo=#Ners49-aQj4ajDG=feEU2-J_svx&hNWd-%zXh3PU``O}SD9Cy8@faLa zunC98#^m$pXIXs0O3M#n-e)jLs0um|@7J6ATZ8DPnGO`PF;dC_@{3oxIj@VWEk##U zoyZmtF+BORxFnT4w?8EryU6<{Py$nP^9vg8REkV4O%%yy>v*Y1%Quszp78Wx<6Yd- z_Sn(x^bYH*E+LrfyO9>1a$+41i9GMZ=1VFBXGy{CDrb+D6J;#aOfA3&Z+i@_TY&5q z`H>SEe;bwTB;*vYFZ@>IzOat1&=@oMtdVgJ*sl+k5$CSD;(1J}I?zfJgebkj*SBp| zwCda|Tfp3r+SCQYn>L3ChU>gk@bp+;!6Q!Mf|wy$D-YB(p499x*6J#7{sir& z#Nw~pr8|pmK>5}whqej0Q>@cN5oe7xBD+aE18e2wq?9VfPLy^bf_{9yjZAUF^JCvR zV3B{YY-eAyvDKj`8Az9LfJ5~OPuc$U+J7(Wmv1~4mfk>TnAG^r>%}hkg4na#hSS2@ z+iGjUGO4{%*U3Q0)_P-ofs+4JHbcn!fItU--lFJJjrBl(d#gWW{WtdEI>7rVX7w(RbHj}rgx?#)m}}mrs_ef{&NB?I zL`y9KAi+3R;h1brPUsDsfKw;)o`Zwf}RB`ILPn>ZztH#zs&e-?ZS5Vz|mF^93 zX}YUypK@tRz%mJ56QaCvdH0`hu#i`VSy?hQU66IxVlxDbmD~&2<-_kOb}0EH6N+)9 zkWK_AlFAGYYj3(pcrKk6h^~dfl`+qBDjf?HW;eAOH30GZL#-CdF-kij-y$nOC&c$$ zac!+(u>mP@vGb*Un<#>AzA6L&v)ZKOFsjY380S}X5Yo=zP8f@U%PN!a5u-*rGqpCh zu_Y_d+Rt+C$!(|t1|fTEt+`M;8xv`2AG=Retun>5YMj*ZJ~sI?gw{4a zCn4ZV#GF|2Wiec8R<;;3rj)Ku;W2TkeG!)va|?Vqx?Dkf-nvN!r?-GV0Xkynw6G4! zsf#j=B8zc{fy2-5anlP;lkvb4`&e&`!BuLp$sZdR%&K}r)le-T=T|3&-=5XJ$(3Q; znx*T$PKi&wOW#sH4gv-}XD<2*HrrdRhrxeR>6yTpsdv^rJoqD^=R_qh;a93_s?mH0 z#6$i(nscYQ$Q=VdS6$P&T|nY9fmc!z`)!h8<5X^4@e`RhRN}CM%63Joize8o(r3-A za>B3_BfAUfd+#ci-nj8#>F}oTP!n!(0%Y8FmA~Wer7sY((EAc?5~aU-t@W_r{Fx%) z6)iZ|{L}Rjj}r!4D+^JCk&4m0W0GCdrXW+vD3r6)NaZWV(G)_B;M4eMwjK}CE3m@; zl#oUOfmmSTk5>C$u|ve64pLjjlH4XN%qUp4XoROWhZJBYAX`MzStq|FEH$#Q)K!Th zZko}ej=-v%Hjj?H0xmEOTxnq&1+pyrR~ouk$SJm;AKGQYs{0Wgwy|QN6ow^)&vQpN z5(4rGXR?OJn_xkk?z>T%F@EbgOlrC=fCY6blFx8^0CRAM3bE-#1LQta%TrwtC8E^5 z3HhuuBsXG1sD&yP?|e#gG5V7l%-d>Tr8n1$bB7S5>aCv;XAd>boMzSLSm|{hdWEr96`w8 zowD`iW<6b+38YiW*u1N(*dGL7}6{{27{YBxlr$M zZ1=4LW}4B~ur3nhs-Jzk?AR`WWEI&Sr(20(-svA*n>}f+E->8O*ry&khM-QXM+M)3lD54f>%!`1LNU)|ns6WHECZA6} zPu5jJum>Rn5VkUPTT8V;A1>}1-g)#vS8$`a>|RtPXV(yqIzUlo%HY|7v{q+hRERQ$ z3>nr}>f5SPe>=9R;$A#bHx^hgHHp{`2Vx?%ev z=y65NX;pH9wtRgUPLjVmTrv3uf+vWlJey>RA~fnZK*?IS43e5B7cPf`lJ78av(&h~ zPlb#CKk2*(9Pl!r>HRBJe;j?Tb$_^@}sc$N?mYDH5b98~Tooa%1mQ&Pqcr?#$} zU;jRFvkg}i!*8e@fWdu7n|(Ki#;U@T^oWF0gdetL)E^?YeD+DWMvOyku~&CpKnJa4w+y)Kf8RsClGa&4n_G@xd)8#lZ7uJ(1OXsLeIGU6{+zZ-O zUg5nWL87U$f2cg3=Jkxku`6*$(v|lvO}uZ$0rvkOrAYIrbHih z3)G}uRKS=@t;GlPXCQ?(VmTmhBv9m0ha?yx$|E^J|0Gs|{8m7bJ}TFb=a5IybD5jA zI7DKhpJC^2QHK5qB(E>bG{CbnTR$V=@CH*mbODyM-1CCx=uVmphS;&Ev@=^OT% z!KW84SAyPFgjFg0C8(y6VF%(QW(8t>&7Hs^ynb959p&Xlb3b zFa(vpHbOUc1|YTHDkM$^4Nt7hmf|hS#+YOcYrONYFDS%=x`55&pl=6SSGi_(Ws?5J zi?Y%3UQw3Ek;i0Gs8$w-EM`q6*WSdtvA&Xwd2W^lnM?60Oef0Gf(etW(#CmrySp&v zX~AA>gyQFgf} zDC3Uir4o7u3?pwWi6As1v}yKq+X?7%?}HYm?GtB8qfA(#W9GJpg4G+u4X}!<+sFz( zd%xQoJU$ZEu#YoT%bMk-9y{MiYQ7$;8-DNNFW_OCl%rvv(1Z9_*G-isH6Pw7zR;Y? zy(Z6FVf1c6qGTZZZ6OS&!CEogB=TvRYnd5mE?2StJZ^0FKH*d`NEXY5ro(nZ(uu#( zikz}^qX7vazzVEt5~NABg|>Mo7t>5QW<=h(3E}+`QkQBggLUykwY>(=o6CW zp4tjB<2RsorBcapNepL?u5Pt4Zmgg3wI(m|!I382C*YANf^rD0=pAgAjm=i$hJKZv z>myMr(Q7|lf(}bu%0{|8&jmd?qh$mkPrA(VVgI5~LC=lBvd^Y7fLOvK5vP);I>65HZeg`iD2RlIBbv7d7G*tgcNgtR+~g z`77y1mC_^Zq{BY5v>gdsWlwt%5Z`*&bFDtOp@lQ$_@qe-BZLS*NQvhg+aTgKv}E6@ zkFXKF&%0aVk{Y!Z;6@=~cEE*)z}MIoEb=_@nO9Kg`6;?huO1LR9K-r<2n4!JSrT}0 z`pH(5N5l%7zR4NrM+JAy%|#BdT5{``dP$ut z3q50PoEM5rnZ4*HhG_FH$l6Nf31dp9*eH6Z92A@apFBl#7mfcFEoM%uT;hlh|5vLY zI1*OaLhnq5u8j;ARXVlYHr3T{0?sZAbw=?=SRjZV&tC0mbqKOfPh-LM5AS2q5h@~= zZgb}T?*#M6-v*8=8Q0Ix`M?ZkT@JAXg)@f!1Vc&`#|xeY?ce8$#$--Z-s$IM)3Cm} zj^3hwnWa`AX)lgz#YetY8+uak3sHHN9;S!`=CkgJv&L^|Bz}Wl5dXCxoO)X4Fqcv< zyQRq!w;RDZkhiZ6QJRYuBFO6-roCRCB29yHs&8y*E{vn)f#O}qg{s^L*Q=J5w+$Ts zIb9$UrU@u!E0-YYj>fKe-18J7;JJ5J!hs4NNt4p(7(QW$Fpa1YE4A!qk`FcQ!0LaL z_#j)y4>y$E1C#}tfQu2M987l;953^qrzd=D$pU1meOjLahfLnu+fog$BLl5(7KDCE zz!S*hh8F)(Z7_WvxrkAPK%uQ|3dCgYLirKOTY8RD;;7X`$g)Q@o!poh zWng3CQi!a_EJ@}9mPwYI*kPMWIu_T@8mXiv-Qk~Q zI@H={&P)%~@W489nn=f@u0^$G1G`aR-6Y^|xms1lUms5KbnTeH>% z*z|O@;kV`g)y+Kj;5a6BXv^67p8VFB=yDu-hJ5V?6+~v=&=yWMzm9cH3y00UI^X&^ zlJQ3(L?g6;aVy6t!at8xYJhE6j>rdCZw@Edj5NnH-7U1YnMSb!gCUu=$Jmcyg+_N- zsy?4eK9BrEVqh8gMAW=nn#3~y!hH82q3Ue{yB>hM_=>%6pdGt%=`{m{H+B1I9yRrAH zU&6_oNLgCJn2Oa{`Y_{KP_yA18O^F&eYL>qdcxD!t-YsOvot^(Cku=q&~>IO-nrzd zuw?y`OiCUtOJd;SLw0C24Nv;~p;V8MYh8P)g}vt?58scbu%g<_JIH|8ib41EiI&Jm zoUcLTFfnz#Nh*=*SSs&!9rD+_PN&P%2JD-F%{#q)9YaC=>Gyd4UU_@$_JyZz<{6@w z`nuLV@$XVJQL{;-qxSq&qp%A@F@?-niF19c7@A4VfWqBfXK;RxI`^c0}&D z#M#geGffU#d`JpZ)7Vr|F-#~p&NXJvcA{?ut0KG6me8i@ymR_{b(T6^f+x{jZCpj$ zh@0Dc#aL)BF4NPj()D9Xe;@rd{{+dAKceelOdG*dt3$9(zMZ|_S{M&b5{%#-okaI{ zx(oq+`hTo!TnCN2JED%3ltS75;PH56OR7Y{gHMmkEPYqd@cDhy2RpCoMupJHXfV*P z(Xr1W39(31--2PH`T1w7+MxrAAgFk_YfM}2!|YAEDR}DWD^bM5?Ns1%)yiY^C4c5oss?#gdXu$)zXX%HaP}8->!#G2>%631Z$oe{ zJ>G$?7NddZ2)izFjD7cE2aAVSN|NH4Eg+Y~hlSx3(WnWB3oH(|V_hr$R>xH$^ks_9 z@Jx=1bK*|pgE7ERbd%uonp5R(S^SEQ&x!1&Y051=KC5_DzrU8IeEB)q&1BdPGRt~$ z@@a3f)L)T+!xt~iHvGJ0PuW4Rn=DudS?3sVb)Fun@1GoE%pu*R(zlGY`>~&|`|RnlB$L7^nsG^I z>rH$-q(ln3Gi7#?oI#zJb2j8R&(5E+0#(ZbpsJsFamtWl_K+`) zJ`7;w%O^zI<3j8 zjn9+(LM`6o!M&*~BdCIm)2`5$C~UPO2g?JA88wBU6PxU*nQAmuT;^BW7FF{{?Aeyh zi%9)rcG)%{IQVPC1N4B3Uw(^wTs{2~4}#6F;^Ycyv=sxQc5i*_!TDLbB=nl*W<|*+ z_fGr4x*iYyRnG+2|lTF%u`@l(VoRi<~nau7MW1JB)yr{zS;erUOBWkN+q)Ry@mxb^+)OEBfk>iV)*S{6D{)Crsd(+uVqv2-2$7-92OuBY z2EFt_ZiIi94T=+Ce7Gy|pAvj8kB&C(@|U8Ies{-liLMgOI!{t`K&x<9Yc0poS^w@& zpuyQJ%V<&C^Z>cN2*?!*G&@ zJhDqqoQ1#A*4*7b$WO4}re3lufngz6BQTH0r|>1BSdOYkxD~+_F9&H(BEn&;Xf42b zcX!hnRxbyfI>Lp8jya>C__bdrJQG6QlFF~Tnbd_)j75hTmbz&j`{i25l~@X%E0_Xd z#=(F7ctQHZ$`i2;Y+-h>-1x56Xwt27ITy!JzcYMAnktTS84#U();mUJyJLY_d~kIG zK6u?^vnbO)EZ0}Xc6TpKiN~&`hvht5IxsNI$02oabzh<-FR4oHJKLFV%j%B~!J!cd zWj#;VHv8S+Lf4Jq$>qt}d0H|*+84mf`Mq8QE+!^pfyy{_GsT+K#N{iZ9V8((s;rGl zzBN#jFsU_i7nqogspIgIuU#PxNe=SsyQErL*@=y)a@&@=V;_|}jUpqusA7oz^%n6# zqG215p2mmvgVIzp9{70Sp6IU*Y5lgsS;Mcji zTNRQEqI)&23DUS@8YTo==hs>WdYY#OX%yd|h>N~ezcPov@?GUM-pT9PT9FQ>DYv{SZ5^yDDZPqrqJ~s z=h1lVN=eDrKCHX8xmBIbO9g|<6b1I;PX#W$^{2gmPu+O|bQ~gzs0&F1ch+@N?8UE! z!@C7G{$*)r$Eks_H7Eb*m}k?L+n#Q|zk+ZUn0w1=p3)p-V!n$szTc!7J zranBTXnu+rtH+htVH^!vl$G$B3b-(4R z9#=ZNwPI}X=my7lH)JTcaI5-hkK!$${(?5|uOu1Wj6F9xG-Ez+2%h*b;w?4G0LXZy zgf0ijD?HBPYz`=+I*47QsxNt>Js|nU6m>{s5N$l&XAEqA!$06bs;n!&yD}XV8c26f z&({(%x{#0OzG-H}B8eRL)m4r`m(ccZkg+7+8m6gO-17`dPlD_xZKz)gz(I5B0FsYd zs*B-UGsY-5qq3$PfoOn-Ll$hIS#=dDQw)wE*ByN?>EEW_4-A!cgTG*45(JDmxSeGk zPm73Ot;op0fBAGdh+uv@j0yOVtNIisz^@ zBtMPfB}Y8<`JMAD(J+4fLnQfQ?Mu1GA4+y&RZvI4Q$~5v`j^+#4PIMvPo}J?(A7sh zjE}q6OQ_R=?GJ?T!P}*aE)QsCeNMH#5ZP7HO$yew_^ZOHXI@GD*E&8#t-_lhdWL_m z14?x83_W`mM{GOvws*d=Pa^gu2#aU96$dy@8Y|eRFI1FE(?|X!Q4&V5{&H)4yN5IW z%KZ2%mXLK^W3^6DaRMe=DSsn+k}JA_o}>(A6}M=5IObg6@7C`xB~rA7UBMScO}|QJ zWDPr?TYlp{ zQ>n%%8p_JS;X7jBiMSdHyL;lMH>7L|><7pu+;mk+WtE#~Vucg*cnLC2B*Z2X*cAsc z_L@hnJVu|}asKpi!610KSZ#K{I3}XJx_S^|Q5zKgBV^DvCp?eP+5D3@q$eFIBbJH+ zQE8jhxW^ToT*i`MVuH`v&kTw=a;IbsMqW1O_Z}6-<4(lA=ZU1Ks*2l`_tovkH!W8$ z>Qr$?1qyvJUp4Kp)kW6jsb>SXxgt9jP*Z9hrO3V2j=?lQH}&T=r>zJ!b^Sv01>LCK zz+IkabPds3iIW5pHW?83w|=z<{zm{JF8^2`rxvPm#>1tm!Z~p3M4EagkA5ji?@}36 zLHC*xhFdBNWjhKlhjS!T4MTI6h_j0HjZ0peb$v4ax7w7H99|2IH+A5*kngdT3k;Yp z=k)1-)Qj)Wl2ItGVZ z1n){17+6Bz_a7f=+?$l1I3JAgBPkBgzn+|%Ce{153wE-M-`}-` zz;k)*i6NAeH17T89nHl`N>T?VIjGAc3EJb3BPb`0m=c|#r-5cyiTmSFA8=Fqg;~*J z_ZxqQt%aqO8>mcAZ$OiiTxOkhkj4B7U8TJMF;w-Mwab(Xs<_vuGNvi;O-r`E=DQ-q z7v+xKM2ZkT+A-R4Ytb1H$Nku`f=;6+-il${zc};6j(l$A3s21Jjt;FF=(*U1Q4|}b z^Z5|=>mWK{pHOg&!c_g7BEA#D9~|EMhHrV10l42!hqy|}CG^Y+>F&rNr-%oWmeLI! zc9j2B4eY2O{aYOj;eKXqI&5Q^X|UkMP@Sb*X(yS z=FD@O9~bUO@7%VC22z`)dCc8U+ZEDP2C1qLn}?Xl+|8&97i}l6Z6w0#QM!@ksmSc9QsknGB6}8*_)hLstSK~_o3a=Tg{B`_x5iBrvaf6?Hq5e znB+!j-X&{{(8#7JQN}-mojq1^hu#a}!bo`NfA{=MV5yXj4{N!5OGa?Tekc)5%7xx? zY=IuF13$5o)#IrkYYmiW&vwZkAjET4@3U}R_KQ?%SOHlE+Su^F{0fMD4E) zsECw5A?EPu-nPT3-=v5aVGdkk#VBL&GAXSKk*-s3p0UPLY`m!%v=r#qp6bC*S>zKi zp6Kz`$m1Oh8QvXMRtCtqddV~PY_juGR#Mvtupi9HQ@TDsBJOaqSo9D|hDWawl=$hD z{l)WN7xC>~iWIyqyTI3RS>+cTQkMdcq&=sb&tTFO7s2~Q}oyBqD_-U`k*!AIk zclg&=y4$5CQj&U*dnqlBUH2Co9(;K$M2*i!$3j_Gzfy6qXiHMbSQPdW zfnp9;&kS&ar?%rF9zCeKVVln-o-uRMeXna3ZH+Md^@E18MPy5Cxu8Otr3lqfbksds zetjXm^8wt&KZ;n@10}t_^?xS`EA`0(6>3Y4PlHla9iiN3k~ZK@*mvkkr^Vp8?KJ8Z z1lv0EjobfskurCDFb5LCwZpA$D4kkn!fRo1$Ai%vm&k(3LHKY_On{6%KZEPaVcJV@sbTdI%?73_dci2zIyiQ(PM zj~aqc`ogg@n?6uXVSIi%ani^}MNn4xZow)0Etna3%2@L8=r+vBqK?<^GB_6(m&xBE zXZJk@^xFb`xc_?-9fZ7=V$wOJx^-#e?DomQsQq%V72^4GG&(q5%CJ2@voM7 z!Xo`jdI17Z3gO1>{3>Xy;_SF|K7UI<_&oM*cc*_&;C75!L>h?B>Y3KF;&1A93w*R# z`0AQ9>Qe~|4$8$+S(yr_N)>iI3Y)!n!d-HiO)Q0vPkEk*_~FC~9wjkrVCnYc1?ue> zZigFwbej^0DX6%-KoZ0~s(;!Kyrnd*6~BuW<;=9K=YFiu-CbF1zyNdJ<1I^8L3Was zX+~s5VFEHsq0y35M|W+h60nOmw{-W9UxI(Gt9H%zHd0|pb3JBkadpE~HLW)D9eMOwz={xk`iS522NLM!bg;R zU1DEE@uua(QOxSTVCEQF8e@0{6Q%@B zOTmeza~nHEU2Alp4d!~oX8O}JVb5+q95=a)%n-#SqQB;MsI2;uYbCM5N8$++~i2;;{L!mo|%+ z-%Du_r1SSyWRx2`st@ipX#D{k9-3vuBZ zVyPd~Gb^dI$EB`9k|p~@fzrLT4|&lBagMNa_+J18K>EL1!!y1+rAICdWT~3&#!pst zpfW`!N}}%-@bu**>`k15(Ps3I1_d*zRx^wRjRPHsHJdGW145X4<0j#$8CKiV zZ1fG)BupbFrunb#SDHh~$PZlCNMg)@HKL)~dRt zR+eDx1g0{*c%|M<0_hgvqVZ(h4YZ(87lst4=p!_BNsUu&j_L88@OdCA*;CJ`)Oj7?@xc$a1Vp(z*&QJ?<;2P|)*n z;8BY~ud2v=wVB~wfxXlu^_icECv5H1f8O88k>_AqfW`7oP1>f_KJfSZ{V&s|OJ<0>Xy-A!tRQuTXvvrF|I2bl ztD;`C&e`-n_kGUuylH=Kf9As3KDB(}<@KK~mlr;t^ETDJ{hO0Dt4y0_24l2VlVCb1 zt7Ur;lsWjpv=|q>E#*PC4@t@k&(37x8Um`YdpGN(j8Y^r`;&o_giA?jP06;M+4AM$ zI;p)$3w5%f8Lu#`98>5^h68PKrB<+AWnTpQNG^8#sr`a+`V9j|1Z%j2cBstMljB#sYoSU=~#8rY>HY`wR9D1a^kD3>> ztTbGT7=WG}7%Y;cjFc&Sz-U7Ka7a0b5~>9)-qU{J_MomnNspA%77?X=pz{QfjN1HY z^nNtM@ya5{inP9Rek@6AF}KCi^*gE+jH^QLw{2S8Px!q5n7YOQd|{Qe`H)ZzuzQWr zo4)=M?m8wmTo8;t-_ziVr5w0kUsm$+Tygy73M!eb(oD42IFht!dinmx?`94(JE zN?;VQ7gz3HVU#qE9M=qGP~b{httO_TwZ@XxMX@)?)wii;+iYgLDGXJ3Q}Rv(cgS+b z6e|H0zArv7Sy3cU(dYro1bv2M^{1&*!tFIaSFyTk{t0$qLjx2<(5@tr(8Hpf{Zx17 zC%}^YQv+(xSgqVrV}E&tEIv8t0N2vl4i*#b^xg?^!e~U6%ixH$Xv_ms&!(zkyEvOS zLDAKol0mm)SOWC-$Kk13GJ~yibg>30qXf9Ue8=W2W#ZHBcOJ_cN8+n%ukzdS+09UV zU=={G2+Zp>C{|pO@a5}OXi2a*_9vEZ4PB*Wy1ah-{jmBuy?MD;ja4v0nW+G@8A`U+ z7p*j<(5JbXQxmq>l}w!d`X8tXQuH*4CN9i&OHr)zm#rRUtVgg2{~)`5dJ)##iF zCDv~vDl2PZjYO)%YIBw@)|7j4D@$+1YoCw%E}E>g4Zw`VV=NXbkH4Pqv?Q%Bzdhdm zoOg-Q%oR!v=V&MK^umXO-l5=F9K)R(Q97}d!{=FSlEeTr%f(n)lkEHrU2*wfOly@B9qL}ZptY^lj^Av@3>>#dY`T+HVaY?#64K%}=b;-0cn07!9&O6vnK$-L1SWgU9C3~oN z3a6FuutcJt)IKLlrVBNRH#+H=MEbBgs&vJ97>1Q3v0s>! z@Ea%s3ZK9bM>-=*=?RRi-Mp`qH9QwVso_-*4@xrx$^a~WFoK3?^tExwHXx~LpK5zg zW)7eEtB&UHN^P5f+M=NHOk~Lm+#@D0p`jir>og?E>Jw%Ll2;$(4+7^-E#xyM5 ze0wqLY_Ew?I>TRdV#21KS4E-k&sALi={gz3wfRF1Cg~OL2eYy!Fg@S@oF){VyoXYhvjRX7;{>i6{WnaP zW2wmgY3tiNu`5eVdv*P@9?5jMuOpGV^r-_lwTwZLDexGppKh;zy#M@b3G|KWxaZDO zFQ7RXLH!`HS+_rL+RDwYA@; zY+ar?c;VzK+5$2lAoe!}rJs>JbA^Hd!?_W7PO#`pW{xBU@&i!@)72&qf?ozX&Y=&g zrVGw=_6O=n!>e>QqTU^IM*=(XB1gxXK0)7y4ppAiBB?qIpT?NzPW~|CQI1Y2WccIZ zbA>(~*N?LM{oQip_ai{dnV^3~QhI1sX@a151L9@)!vQ)f$pXWPQkffBP zEGpejTSzZ$DOB#bP~+sdK7YmHYR^aYj%e zIz3<$rQEhLhf)|1Y}mb`xEF?@zhe%n5vMUo)rC@hY?+5dPwPtVj_Yj{rp-j{Ix}KQ z&a5hT^dDO)+V~J^0gLFap(#7*dUE8AmT>OcBP`LHVcT0!xx*cm>BJj`-I#>IdM5>Y z$Rd78ja$-kIQeFv+(*c?Wz3;4l1yt-IUdiM~&W4F#0nkL0x` zGm{%bXrm~o0K?0xwKb5kE)`{Ecq}zn1QpM4uUXi9V@*N{<{UUlkCp_ftkCi0_N*f+ zPz&_KQ?;ox!R5>wpL-puUc%z@@sk0X)#{Uaz(^Fxw8-3Ee}BCHoag71a(R?SA)#=5 znxq}&o?ccmNqM}#?E{1o6_GiZlgLoP8~|?LmZjpc5xUJ!WFPe|K1yNPqBAKtAiow9 zmMddLbfpuYn#^NZMvrotG87w54(+`XK6*Yblb}YD+RtfvmK4zOltJq{^%FaW=*Sdj zl#<=B)^NiYTU6SsZzzJo!gzcU@ZB&bovjp-(q$3lPvv^S7 zXT#7Xjp1?wcJ#g=hx*eR;!E9tycRD!>9AwW0&ILMrUcD;{2y=<1yq1pt9bI(`BKLL zLVAr!jeb-Qn^jHsgQ56KyU5p<-;PKGbl5q@S-U6r$L!zbdf%K3W7$2aO80M0UGcnX zKwn=srT^zZgo_`T)rN8S^?F+}-{<{j;XD`&ECcn0;hXdF^2b_LJRcL%FzGJXPZrB< z>MLd=wsfjVH!$YB0>t#nP(OFT)tfWpEtyCp*&!&O<;ZBMvO4NRfL_~6;aq}=nuXqH zU{97{vw|w!(K2x}4z0Yy5d_2tV@E;J;BtusHJOED%VIFW0YUgU1<=JRSj-2@6sWwK z#|cuPCZ%9nqdk-|#QUpBf{L#)qtTz@(o~t0WG%p!b+%hHYKb{JHN&o2lz7BQU81It z(I$cA$qX}rJha{Ht-t~oxq^#Xi#@7RyQrnV(cBK{8Nk?)WyGu6@Kwu_Il9f-t%UyH z=YlY1Z&`E=)9ZhlpO0;9ulpIgK|bWJ>Hd;mZZE5({rk@x#Oq|Y7~Wb1sk&CT*OjIB zeBLp#jYP@%sh(RM>GTqyFY8kAR~Zp%txs``CAg}{UhV<6*WaFx_vieeUif`UOZ_33 zE>@k3hMq0Zk(z6#j-d8$y%^p%jj)v#+S&2gu!u}#6LF^rX4IO7Z3dz?%<3MZ>CJ8g$Af@)K&dq#Fsxwr8`aK z5c>e?;69QOdY*x2tn9|0QIxWh8j*8D0NTq2GldRFinT70q|SqeYO(|wJ%zjI)B+VB zLmVJ&pPdPOVK`HV-BFxDu>BcAp^QP3>g366Wvwo~J383vC4X_VUL(zjF!Df@P5s zX|})2O(0E#n-4b4Hk`exHQj$s6LhLU$8XU%orHFx*B{O6mX?Bmd_3PbiP6bGt~E*T zl3F5b?3U8K`bbPr4q7W<>BV3eVAr5#F8!_t4a}5VGog)I*)rtdHcbJH>2>W6OsHa} zS-eYH{r=LT6TB3%@L|t{6rnk27Gij@GZw(pi4Jg}m)veBM&tH~V1Q&FiM$Ay@7re=>+FHXK)QDHcO)bOhJP^E_7Iy) zp503)v_)>_D?!H^=>QCDh3_#e$RHnPXQ*wPOJH4Iu;=5aHSKVW-_jjIU}#amOVj1$ zdR?OY|5P)PGWeAPjM#$E`dZJ%55w*IANQYsZZzs?0;EeMT>7ohn1xy?4UCOqak)O9 zZ=4@%fF^=p&kEA3Z-kJIDPyePzI~tXKjz2V<)TE`najdbHY2lAlA#idb%k^?5SjC9 z?t7*H-OKM%ed<}x=0d3O7`;hI7NyBmaq3hlA62z0z@a2(6dz=l%nwRt7Vv`ROYX>e zmZMNx1LmD0wSwRbZE_@G%>-i)-k<)%wbY(vhfH*H4pKht+!6FaLd($8OlQdyTZjSC z&b@2g%uEsB--^&0n~qs_bV*-2IUCBLYk7%=ZWG&v=h>$1*_4}7(_T7|X}}~JUy@d* z4JwY37o#+nXih4XBIf2RfD=L3S@UI?yC{Vxi2Q~!6^NL5W|Y9vV1YWnldVf6mhpBGniGZnLn*3~=iA3tkYt*kk9DqE8+mZI-8S>0 zC1br3$FR)2Paz_P?c1=q;X?a0m>^>}TU&A3oD5gpjmN54+AJP1?H`&>+vfa)bM=MS ziDU8Mul%9gM}2K0{PG?K_Sxh|8RaKO|C!Ll-}*zGUY7n|@uYS7OPT10<2!#l{T(T! z@;~n~eL1cE(w!SK+{TU6G4bpdc7F0K;wvZisp=F@Tdf_r;y>x4b9mL-{w^=SEzxhgC~Q(_S`_Ba^Wk5dJB;N18j%opqt5b)si)uQfXw za#Pc%0jAXPjyBC9a)@(@!wQbh8kxcPxocPW%l?e?K#k#ru^S*;49>>dQW2JRb~4M{BtG3tmuhCv*( zp+ZUh8I?>Xads|)BICLt)qK)LN%y3AN<%d@_ZvlDURLMq+ZLdobpwaxRL_2hyc0IP zvP8rsKtJC9;#R=MeaRG+oCyL&rz8)94W#W@6pB z^Ao@xRgZeW`yM`<7#LGg``5!W22T&58}?xDzg@=aRHxX=AivE&6no;Mb-%iwtkLXE z9$|KgW;%bSDIZ*})9vN{^Uref4E5$=P%V@FZN2Vdr|VKN5V0LykkJUs>h3z<-%iX> z^&8U%_35(SlzB;@?`IQFQ>4aD=Vdv&2=U=kg)}>5n$R-Zn{%!<-9<5a&fdRi2>oC6 zk&U3D{43?RQl?NWn3HFrH@SJIAnroWjuyjIuh_;Zk~9;wCJ3!sBDwU6bS<2C#}5CX z&RDn>-l0m_>B$}vsZcuAKBIC*?;iT*wX{A&bn>f;(8s7}BBIG~Pal4?km4+THZErc z_SIQPz|8@e3stO_G!oX3$VdU9S3X}ZEsoK79*dTT`a?x>>&B-Ln}Uwn#!fHLcxwgi zDG)@MoncJK`oQrTa8FzM0mz`lN5Zr_(c^nCoBFf|!GIRZhD_rFX9%5dKC zjqA&jt**S|7#az2evEsRMWmcI@wp`?qAW+E4h(xzTnsYKB7_L#CQg@W8|B>H4O z+L4x;+F=nr@1AtoBWbFIe;||oGkHSLz*lZ8Lgs_8L=H$Dw67-7qgjH=4AqK|LI?*2 zq~C-ryZveF-FL|mWLF=+p-4Hbgg~)hZlR>MQwPcPK+zuRN70#?sL5Zk*=fKHNJ5PQ zX_R*^a8=|ybUfO+38V&T8IjzT zUAazpFG=fT3D6a_fNQ0cWOhKC?>fGmuB#ZxN~0ExsJCfQ#3rO%5B%$Lxh;QL@r~|R z+TSPo@V1npq`G0y#{k>Zv5Wt}rb$|U+$bS5p=ahASQzjJ^)dJNJHHVtoC`g`YsgMh zFBrw>%uaBs)o!r=`a|A zS-KnPCavj`&3Tww%bGd1830!P-SzezxEut#T*T&o|EZeW^g|0icx*o{R@w<}k)ujlh?J zU^B`>*LB!$_0$*mZhw`NXN81Ca)UWr<0&&_n846^YT7=e;?Bv&Kw>H56?=z9xW$8t2% z7-{x#xt8d>O(Fj1(t+?RG|;b%21+;A+tnhr3TRd%Zxnl>O!*YXh}@jOuP-a#c^lg? zt2D%sOcKLL@c~JyWwFA~o^T4`_SCrfjD*IH%#jGVsiiW&K``!1l`Ej0eU3B&^4yP? z*=iuoPnYIm3U7G&xLfCkYllL9yb~<2=nSnmY%YeTt_f$Ibr}98H|Izacw|Mn!(PhBE7tg+6*Zsy(#psRE0HZHI?Y1 zv9s%S(7~VU%OCUeeQxfqDIx&y1w}LB2A-@wESD`nOD4mnq=BT7Gj#u4t!Agob%Q3* zDZyaac+8(+xm>Q(awO09CA9bzhMIg`8{Hy_Up4cuHi{NCY#pIiS1qM*x=2h#LfXVC zMmj#gG3N?03@J+;+&H>~L~#E@xwy6ys+=Cw;S(MH=MHl%n~ea1f^0`coh+ocH>jAW z(cq#^;(qKZ#@U|p3?lk$IjaVnqT>+?h-9nI7_vw6i6ixR1(=WV%q9*IGuwmSK2N)a zOwWU!s3RV2teJ}8GGOjzMc2)mem0Fre?{cd9K+_>wkPfyWZnvypn+qmS<7gd6RlFW zFzx~MC{S@_5;2mBsJtYHltJsNy>Lt#p#h6DDJ7(Ne6q-c+x`i?!l>fnb+0!{RwB4) z0Hb8gvx;+ztwK`Wn)B1;b;%Vs!3v6Yf0fabMubg8+JO72-u&|YkNex7L5D>1)an??V$#G_#Ralj0twI=v=xig1hpmRGM6#TAfblU6I$Z(u z+#F!wZP*EPD2oN?1jzN>FvOclLIp(B!+$*2GfIKi~f}?U9)Dj0=3^K4GF1)T$H#?G6Z+FpUune`TO^OUaEm5 zm!Ek=-Fa?n-#VKZ55Cs6n9F<|)^u61@PcW@NlZOx%jchKO9+qE-g`2|xdsRKQ`m~? zliKhLL!QKohD)O+wwk-dsp-n`U^>C#Y`C$DZW8Fj_jN?z!m!pfKNtmqD~cySDyl2%B26UoXe;Xem(-?N`# zKH-@R7%MVrMIn5?vo-}!8J}b6dN;@|Dk^F-C+;vO@|l2eS=eP(shVy-$eK@}b`UkH zfCI_4()D!r4L}J15UoBS-xqZtc9s~877bSaD!40EV|%gDCSePXyGemg8ey$=qZzP@ z>o5P=8PIXml$25Moc32AvBvfKeV(7s#~lfPfZYwDno!%9^LA&i*VpNCeZ2jJVr|o! zxUA9kumWO;wI|`d4Q%;+Ii2Nbj!CeZurO+bHX9M9q?k(+8k<(lrxhewcM0t_uUB+a zXxbgZon{v@?{Gj>2L+iklO%M@uBli-RZ2^5`_`P);!EkRSLwaU&hih2geEtwGdg#y zB^`@ZYVFcW95KF;c|^M0G!OTHdVjz^_fpeT*nxEVYKe~R3MUF6`K+e_=i?Czi{|Q+ zu5a5%jXFx@WOON(k>)KjyzJB@mL`~RT)07!j4wGE!gw>Kw|~V!BQYiDFls~y{|q5x zq*@`-=iQIm7Uv9Hn;wQ!K7u{wc`O^atyBq^vc76@^x!FssxD+lF+tQWqcC=uJFdOj zxk!D-W3rtknR4xs_4fK3m%MislJ1&}2msl-3r7#I39xLE)@#G_O^EuX^1tmGP94e@ z5h#Ky!oE95X5_e~1{79B&1M*(+2VX=58ye9OiI!-Etotz zQ!Zb%hBPJQD3fn5sL%YEVzjgXlXOwmbSF9j6}o}!ZfZH!r?{X%%ZgD&Z7Ia zH~&8792_NOnaP{W;cXNjT5KYdG0{UhnXErEeA0Ke%15bNI*uvgWuO2PN@?I&_@dG| z=-jKSLTO}_BJR*hE*qKlO^7W@NJXj{p~XL^RRyT2jmzcb+w=aT(x`>NYBS*mu{9diek2@&PpU?M=xe=5~7v6)i>x@i}-8uIC`he`Y6yd6L&=d!#PO=kv7lhBP9Y#&xA^-d?_~$n0vARhfy-Cw0!5oA%W3IbC0- z+w1-9Pvw|KA&HK(GeD=-k(=AgYQMedlwz>Eo|xD z1tEM2rp_EoKfA`>&cs+ULZ%M}h*xD9ZcrLZbi5Dw7>e|oXonPGC<&&PZV&PpB8PJZ zBd6&W$dzS6pUoI#sW@rM1+&@kSGy>PL712-3@SkMRZWm2EI>cu)qjm3O&E!Y#m2{& zl&R>wI%`*?W!*=E8ve#^E?nLJ*qEA~+k~!m%)IM?lXiPV5@g!FhNpw*ETi=g)~y5h zN!5bf@|-TWC89mw|2j+%%uG65fu>qAnlYbV?t~l2duceJ+#p=l-Hkx7GA^rTV4U46#JFH~A{;D|7H_5}Gn? zRvK^3e5o2cCh^TWs@4u8hoeqg__XsV#uWz#tNE`mqpoeWq&`MRXy#eDSn~p7KSs$# z8S~#c?1=nByBziK+{h>~J4on_qZ(VGOIzsCm^s@HS1mMeEoIYH_O+|Vd`umNxM@jw zB;k?S9xe@aIQk?iC>EzMh!lV|m0^qDDf9VPr0!^I9P*5DbtG_`J>wNLzBUIrN|g$O zGIC|o2BphjlDQ2zo<2f2Z)G_T3SF6o%hcyOWH89MsnwGEK&Y3Z%c)VRy0~)Kg(=$wp>Ay+wU`=t<9L5Tu&nKC9Zd` z(aFBXVadf`uC0JPTHcS)v7?!1l3nJage&%1$$y*YJDa%vD4`q({QGxqum4oayo>Ai zM_;ro@q9ewn%2mAn=Y@9_rDS_HBi*=LQUJ`QVm=#*HuwueijZ4$p;gcsVln|Cng|31`P}OJ1pbARYbR~@$BQ%3!%gq`J%)De)SqAIhSmziV zIG~jxBMOI*4`ceVfiL~^ANYBo_Jy%NL5$g$YK{fToQyjm>S|QweCPdD=p0&4vL{#d78R+<09Dh*UDUN|-oCW$|jm+Nhb(9idu@a}Di zu_j}=UrT4-3R>lmmr#~r?Fnz{P{%Px*d?S$>aR&uRAeUKDw^Is62bMUz zrBkD>>!W!ae-J}qub6a(D6dZ*VI95HUr(bQZ1KZHTgI60(CKr`^DKG%%A9^)uOniEKdn2Da4+P=NO>$uEY*cIRV|z>P`}_UvAB9it75MIh)xRrNYl^!5XS%$+ zzLc$3dAVNJr*`Hx?M;;xt+UhmZ$xn7bbDE=iX94%BVnj3Oq-0-rh2pn>1(KMH2OUh zh^j#A6wT= z5uKxNLeXQWDVjbMLlbSlNRbG# z0V+eyVx#i1xHgd!h6CDV^+J8lyFgOIgR7YYe1tlsNXJr5USGd2q4|0LOAQQSL9fwa zQ$5?eF;MmL1IAs)#J~Og_y4~A-b67*i^D+EpvFp3o{;>Xv04z8$KwYs%O)=CJ~3|- z-pAoCzjT?V9#`0d&Nd~^4!}gY7bDn^47*h9OvJP!P?zdod~zfnSn8@_nn86j`Ne$sgMdxsTs0o+gPz95=aj@~Fsnu= z!(8Qv`s9%L6znx_!9j(ja$n~VE28p*i;6C(=m#?wAw3|N!h|wWrt9_Px5wL`yHu_A1NT4*1*t?v4Z6rrt3nc=^YiUz!<8fCq+^R0A{tmcCbPfo z_y&fleo+;yje~dTN)ds&m(_jlEeSyJ8A7km@sU@83?jdszt)nr{NuDAPN|8qOT zsrLD`6@vIC_QbUFz$nNbXGv2p%VO}9u0Jx3O4|BHB|h^R_J>V#Qx2QPaIc`2^QesYbHVnm)F?6ihUBJcH}!w`5KHu}E6S#z^JB!Y#vUU^bu zvy{)lfPN@^KuYuFGda+F#@{%}s$m24(OV5r*9Rgw>gUx(Q^{FL-ovQFN-8hDg0YOD z&*W0Zv6hhts}wVxk{&y93*Et`GMM;qAdd+(fixB~^$$lR`u=?XYotLc+EF9H#MaB4 zF1Kk~XaD9lCQf7lRgUO`+8$X|mw$h(GI$T|1Y$UFJ}8a%;%S{_Tmk6E`(UgW$OBKE ztIaVGdAqGy;_5R?s8>(hxr%<&CSeShg%oipXm}f4#|~*B(k4xOkeM;zoJ?4oh(#0C zXOnWKi*Cn9luuSIIn}(y-3M(viS~B3bFMQJJ~5Ly5O5thi0O*+c6Wg*>1;97@5@M@ ziGFhKE5Olk_o)+CaF|)!kR8}4ek?o<-ON!qMeA_l=*E-^sDVl~uR=tAfCvcVvg*}9 z40VO+&GH3l4xF=O*rrU=qQuh6TI;NcRvs1`Al^G1EC@Z4-Ya1kA5+!?i39K%Oc;Yf zA|JyiBw`8Cu|Y71wmZ)&QLKv&`Z2`iA>LG?r@SikZ>g^@Vk+8<5dDZS7Y&sa-?;8m zA%9^)%)d0dZLusV+UP4#e{rlsA>TF~4>zfqy5!;b*RRR46pHf8hW?BIgu32^mT&Z!ZH`RQ9Ph%yCr7`vHGkTcQ z<}>^LPIA;K8DHY5rky2G-YF>2fg48NWqGvCBDzpoIP#DjN>{r4W)}fNsx$GjoBLjp zDNWgB(~D!I$w36x8ATsh&w?Z=wLTnHglZBa+2&2FN9Fp1T8PBtE3zG|=u5$=)G@PC z@?^A6Z&RmenpRaBbq95Lj>^1l&>jNr6G!s_>Z;;`nca@kpMN7n(b86moKj3MC&fJW z^w7`VejUXi;QG^s3u6qAI4ciD#O?>IbSXKoUo|)l&$9Jhht(u5Qn5@O$46GPLz`2g zPv-~&uWzDmjG4s9wd^~@gu)VyqS{(Z$n}a&YpVEdzW-eD+0w%|x?vO{nN=Rc`qfGT zxxCDew<12X$Rbc?2WXbL)kf|1?T;NKnV|P28LrTB@rh<>2rlXK>hAiuPk?6X5m}Gd zBUA#8tbcdI+CH;d#IC~AyJG6JPn@GD2|jA1b{^B0Ajy&^zXwPlKVQUCc($5TH~%r! zf1J_7Ny#6a(>NC%9^oem?iYag%zNS(%S+_jN8^a43RLw5o2tiIwDjB>9s(2_U2{mo-1Su^I=V6Khsn!fJ_l_f*Yo^)i zoD^4x@EF6c&jXv_OD)nUG%6TQh#xuK4j@TQD{QI$TP1*Ubp5Z+RDEFOY|`H}YlR}b zJ9fdjR1dSzI$&5HD5K2?X6S7TtLi}o`G%{|uPi01sd8-p7_K7JCiWoLb&DBjg}aDC zAY{;xV`o805~XG)ZL&uDGuMx*#oo|cFaV-uhjGt9lWxd~bk^Xlyl}5>i^XV1t$}J< zlGcBkpKn_$Y>M1aW_$FEkv>r$!p(=}vRTA}NE47Y1(^(vA{SmZvMv?nx!Ov(`kqe@ zS-cDodH-_F<)_Qb1d0hC5_ zXvXyzEl>027%vERyWLOk#Rt;G#FwOKMa+f@50H9|28Khz9 zAqGXzD+K~lqQ1&@f>DEbd>*rUjno?;G4Q2e8K-Po-a9m${wL;LTpQh=qGO69_Fe2X z{Kc1w-S4D+FsC{YLDSVsy=TB1@x`9Lk>bU5O_)1wcojhiAfOZMyz(C14dRpNgWf1n zV+ZcFh+et`XJVR?gFSRPQgR-Z`6}+5aPEBUWTU6HWZQ9IDQGWCP29aSAxVb@RP0>4 zp1?*(TqXn@=0$3lKrLy8M%(ts% zcDKM6+~gJT!ouTt%E65}@z$H2Xk4pLp50z*P=J?eWN#k!WZP*m1sQiJ@~~&L)Gjz( zGs8sQtn6oVqK1!b5t2h$gVesx<7}*Oq+sNBqdFj(Jr@UL zmZnC|!_Mufe)`_Gk8$c4>%Jv|BLo{Hk!|~5WWQL{s722oabKXGUo%tDr-hwL4+3|< zVLpn`E)}_n+^H85WMfj$8Jd2!2024t^REIdz7~i+ae!EuRKB@q#K7zb^-7?apXwgn z!sZbnYo&TXt2WRX=Rp3S8pcI3iN=(MG;s#mp>o@TWV>ESWuc76=bX!zt=bq!Du`|< zo*qlLMRzwCTh= zlxRbR;uWpMF~rR^xgg%U+3}&+(RDL0WhKU$$;ZTy8kksaml>W<x4m=_+BUg-;rXD`wFY?$UhtUue-kJjd{>xQhAs#V%^e-P+o%yr|T z#=~IjQPWpH7n^(7iL**@dQ+MkyE}McQ;HpELX+u^)FJJ}8xmd??u`Y}zn7apNAwt& z9t-GBGYOZT6^QwLgOF9Aay(xzzm}G>xHEKVaWpS*?=7(gU+2kQj?LZ!NAAoo8}C-N z{LCo!`DL0e+1u!$9||jOUry6)nlbHeZ>RAUho|?q|GU&yXL6P*C(B-Dnx;+t9jIK# zr>BI|<@)1txrt~v=3*-yIj;-bf@di@@Ha*A9X1`Rv+1Ru9P0xk{<*tD88q+M8(f(0 z1SP?tAOuh^$DIvUTtRB`4!yS#^jHIXg4WyE3nrDUiku&{+qLJ4)ifg_1reosHM_k}vIH^pKet3$^RBa^rUt#rc&eq%XKPaT zS@McWFqZxFuF@^{sETUYwE4WVGFKTR+@8Zd)$F%jy@6=#A$Z4$+I<}r^GA1*2fEHi+`Sv; zZXW?l_tDYEXz!enJL5PPCY|o_ zrrmz4?^V~Co9{J)byVOx+h*FjyZz|p0lR_}=rX`yht((HA$2FbWhhvQ-I9TkLguLh zB`AVY@PAE{Oz{l z8@2mDLMu22n!Bh3a4+R~Jil&GI3pYr{>r{;d|N8c%ZA`%i-_&c7SZs5mdba#tv+bd z_hNT=q$@@=T}Ty0yJc003e~i^&Iajijg=0-2H9)4P?Qhu1Q$W-<0}jr=?a@1T21bh zGG9D@HJcDk=wd3i(yVPq$(?m{McK)OECL1*N7?K$K%Q)vaW#C)WiYbN4DRiBk;KKf zQ$~>-GR-En+<7S0xq_wCh;i(2^L1kT!cIIL99h_~CTM%RdBcI#g8X^9RCpH?_` zx9v)EprH!DktnONgU{|`9Ug(`7-;M7+wP_z#jrEd^kN;c9#RiOBgjP`4JC3hrC_96 zS#2%^ojSQnUIDk+8x5Ji@ifnS;E0@D^o&bwrO^Rfp2?jv2_y%2`*pHL$9Xdu8qAhdP35!m*($bp&&h9Fuh(4&&|m#%(x-z%q2iILExRaQZnX!r*ZGcn?!x z5ylUGJe>b;>7Kb-yA5t869a(CUKsISZcvmjs7%Nhd&)jDz8lib(4M*Xc8U!{2CN%) ztspy{9{gO-{8!;4tOetRlBPW#3*U1@`5@uhQISK^^y4uVg{;e}IGHuJ?&1p{!ocYx zAUhSI(ZK~3A3NdXLU7y(2=M8*DJ~;)?%Q#oP+0WN)KEXGHXSf?@!Somr_8!KF5yp1$RelWp)E4V`h$YCRk&v7hUY98WLH z>A2=F7e`?4a#q`!!cc;>x7VMm6hzo#L7n2t5~UF&nPU1=wIV8kW6CX7Km3*SOxxwx z;1<^Q&|ErN$EC_zsJ$2!@QBcTvi>M)Y#aD^7+a6IE?WvT}RZJ8OmoqF%dN@UV*vQq0%OL5_Y@PXE^roMX~ zP;vtgSVj;X77}LRpQo)Lk^$(sIMv8`=Xr*-2EQ;thn}7O9r2ii&fcIHbR05aYV6>J ze3fDR=zu7=KtY|zIp$po=uVJ-n?_2}BuN3TEN5n=+$%Bw{f5ds>m?2ERC1S z^F6A|ivnVGyX3I4A#VuQ#M>)c-C+z)4-QN)yU}v-tKTo{If&%kHV4bMMG*s0*TXVO z(O9ZdrHXTuBaw(OMMX`r?=}h#!yr`g2)5hm63f*JOX?=#4!8)JjX(|bphJ7IRVf7~ zy%Q%IciPqyGSU~l+y=@yb1kBiLV8O&2gEckr=~P=&oI4Dv6uWq2DG+pq1`{xHQm%t z;aj-yK7jKBtbD0}>L3ISeD#lSbZqtJ~)=>h^^kcQ{u?nRr7 z@|7BgbXPi!i17RCF8i6(p#1~m``x?E}x5lCZb8H#n{9$#2_vY`#4_by5lq=kiboYZtk$C|!jgN?mlZKMSY zHftj2FcU|bMMdd)?A3ZE(LxL=Q-UB(p72QaxPlSz*gh@-&DMu4)*IeCV#&W8qP8nS zOQv8F<^r9p{xj#qKA!_#7md=?ryAg>rKZj(U`F%8bVk!ARZekZ^O+n|Nznx5($xWx zYvaL~*%l5YB!UZaLZ84`dF2RkwU80;js(=uA34hLcwWlcZDTGgUd&!Ps|c@Y_MBX; zGhT0(-`QmeKEhXAgw(hIp-Co(LuM||x653bR?)yKbGo>TRtU?34acX~rIDZKs|!eQ zG)SPe998qsl#VL^ef{`-&-*W^Taq$TcP|c>Qqp>7A_A#KAZh8)j2K9Y+eYy`hStYI zG@{O#Vyor|MqQU6ZxZ5m%BD(bpo&VUmQ5F1zZ>2M&eeiV^Lu)@JbU@%00=<$zluN0 z3EcT`mBmDFWS2B?%n( zfeI1!VX}ktD6#Tzi}SH_39tn;=vV$$Q|*wUD)5F^2MEm>Cb)K7d0O4o8K?It2Sm-$ z9xGf3c%w0q!IWor1Kt8ejP2Moahgj~!C@9cry{3>(wx$&3VBX>DEG6z5;y1qse^JS z$ZcWE%5Xek*n1lbhT<))`_e&_gy#C?yf>Yn=!|hn^h#Nqw!wL^w&jLZ2>cT%v^!ff&hcM zT5jAZuw8Y0^rQ~jRvT<#8I*4frV@vsqY*IJU(SG^!tNC;BFfhwoSkaTtfg zd0GvUsw*-?>|)&eAdIXpvHoQ|eOsH>n>EH|ltenkuX}ry%jRDGY5DEx{TIx?7QMvC zR4M*3vKGiMl2TS7uDN||&GFYtI_2_B$Sj~XwOJ{1dRn^q%lnVzzsWpiU|G*ytSu{L zaJx<0zsLRADebgEVVg7UPALaGcq&7xE*?fp2Bto3HMb6}#kMbH;SP-^ef^EL;~C~mK+j2fuMDfM)rXo%oSSg?w$1dA{A z%Z!VGcfj0FC@muuHVPL;esGeOie2vvRuaE=;|{s(J{vtKYtZarJic7s|ICIJ0-}o$O@;2& z&@sthIIK|kbh}JhpR&K(Fra<_CMWsWNyk%`lV}|~o?mab_j$ey!;WM)t>1dL9oCm! z+GIo#DM~AUT55Qj-cIME%DC#1Q`Yji)SXpgKBxSO;~|^*<cs>wU~%;kiUMOLi+jg!s465f zVCW?GpQeJS*dwVA8$jzSoh1Ne@@*{weWP32QKWX46KHEDUPZA$^@xtsB_``OXx$7Y z3tftaEHj1Dv2B}x7E_FIDoRf?K=J+OpnMUz3nYJX#l-YmH2w_8;I0M{`!r!ZDadOmE*MC zd7VA-BZvMyuhNp~E|uOThxAv*|4-u^E73y5cTl(R0a{CTBb{MI-|rClfx zfVhF#Uu8T$=lqoG`|i*WHxESc*#wnGP+IF22om%+nj1 zZM5mW)WiF_!BuLL{6vjiqlN47!OQ}0w@V0WloYnecLtg+@AY2hU*y;7F1#@emgGW2 z>Q+J=T+-20DHv5e1F4$UF?qoqeUh*Gk%`}F^1+x+s(`?Ri)1=bM~|i-M2~ow#9c@^ zdq%DbtiGMBl9UW-N>NQM-59*%8c33TrAW zG^DCyxe5>DZR`8vPrAqyX)$sPYrqLId;Ksc!K>i1BH;52_oSq zNvP1f)vqe5RqWn+wPzdpX_eDMszJZ&m0VS~+(^{PsdOAp-s{8w^6p7annKXx}1TD znK5==qpydQ*Zjq$c|M%B0Ph)q7o&tQ zBvn@?Cpz6!kzrS?>oOgGxV2a}GP7&3C2Q4lY&#yH4RCTl%Dany0Io}93JDysy1NNi)l9*x2y}Ga49_|5(9^pR0i{RQ869gz zl_Om;ZmikjPQ5efJ=;Z!XSuf%t`y7`g@TIMD|Q7x%(ZmVKB2%Jdn*gv{cRS4E`H%T_TMBd~@B@Z<2Lm1nDv{Hak>e+x(iETuGrcF&iNXw+8PW6%d`SG(*g)Q;WvHaaDp6oo3@tF|Pr zS5qmQap;*aKcBC7l760d9A6&hf)vM!Mc z62~>#^)HoD(-_JM4CpKv*2sdx;WUiLdHUcqdGR6a1Z(5!UwMDe>^%2;`}jG}GrWtx z^`-LlLH$J}hw$|LZJuxIz_Z-g$s^ZD&iJq6i?STcaVg0w>5vgxsWF|ZhZhZbL6Uel zo|iqrknT>`9`vw&dFQ=OH;$svZBzLO1&$c$UEJ_de}qpuaeneXeDGvHjQ+dl#s1lR zl%3HWAILSMnxlEQKm=p@bhmPEINRPR&Dr@J=k8@ytAFn1vm-t5 zegE>Gj?g;Xe83$r26ygm_L9EP-=m^Oj!DhVs(?PPLunppKI?|onXJNJc$ak!;q z-7as4%Om^dSKXoIvQ!+OKi>b$Jhg{3OWq&NVjOdR%JpNKu8mZPcdxYnqyv`r`KfZK zQ{zO16x}$xf5y{!S#t(<_wm-)!fll|q&V2pVH!|d5be3QMv96F+lKn@IQAbM?=`iheclZ4bv0t5gnImW17 z4c(Gd83Y}V8z_V=d|kAHW(A=uR~3$ja~YSO&6vZ52w)(ozEFR9C>{0|`gi@k8e@hG@zTdrHuet8tp#ex+1vANzP-7< z1`t^fJ%M!XXvbq&D&Vsi*1KqcA@~s0ynm3FrQ&t@%kA>ZyaS;@>hNNHK!kynr!r)l z|KsiBx5Zr`1>%!v5^MCi=5=L4_wqmPC%b`v$+#r*QEwOlfL^=EhMPb>pw;m!jZ8!F zX!YNJqj3s`I34`+zV8U`FVnpHW_Wa%pE*jPi_yAM(ese+Z$v&nO);JmkC}~MJN5W< z&y@b9Kj@X(f771_<)X8h=p6~Z+JNKnHq7=S7=i_fK*(BI@ zLt7#y-ByThx*dzEfpr`e)F1|BPb8TiXmcN|Sly06us6zJ(nJhkk2=}m#iK-N2m1_h z^fwlC(?FySA%fjCyK}Z4tP60^0-d9;I5o2u5tJnSOfUp7=7ET>&@T!MSjIjY)2`U3 zzE;#49+&pGNpNTLp(l-3s6}9#LS;zf@hKOg>7v%Qke-npEZ^YEQg5v^MVNH1&f;0A@CZrAtB(lfI>blhnTvo_XDbwpW? zB%{xyrokiFG$F=_ils{a=3%MwIpgp$rjjhQvx0y=cxeIbk|mAaHn>enWl7qLwiL8% zz_!Dz!-$=-6c-{V6|Nh1G4v26aFQRf5xMI2h|VBh;TpJHGfxi*pci{|^xe=TCHc@Y z{Bx%a?vZD7&v(JO66trBroqJcH8%2Hf9?}CXW6nDncS$0E!s_y&NF80CQFKKj}e{7 zjQ~wMe~Y*v6?lNcHAd?xka$lm@L9`;`w;&Q)DXQ$RTJU*#(Vb!aPc!Dtu_girf2Hq zEVOkh5~Pb8`yxt&RTutnTVvU1ImHZ3JpD0Uf2EzjAq;!ErGrYoj_`NR-sYOUEzV!U z6z^uWlM<$$f9m-3ErTS}M{9wKdd6_Lt|lIO0p~5do6$ASr6t#K z{^2+rp5A`{^EORM#^wWXFP;~hZKz=wGcvw$T+YcnOrx8L!?=>9^8fai9`5)RVmsKGE;feniO&`Q;Jh1@GaD>*eTa26i?5vP zaBP-fx=XUfV+ULbH~K=tc_M60Oz62pQPvbl?f8A5rlK3b?rcGaRk>2m?rJ23EN2zjy6 z$M_s^$ygtke)D|!{r|{Gte4EiD_Nwi8G#Yz(YpL66BUle>*?_?SeMt9J*rJJ`Tm*t%@7u!^|%R6W9rp5`sP2}E} zpY!=2w>e$j-*1;2#J1>oJRH_=7qH#|*78i#Z9B+y?>?VjUeoaO``7=PU+1xsS)sN; z^)~c-ty0!3idXt%jfvNto0r1Mx@d0(XO`o9-Qbe5*+E56n{l;HYcawQRj+OuaSTqY zv$0}8z5=savM@OII&p;KqVF3W%8k0Bpn?SIhF@1qr$R(rIaIhLEz$OT&OQ~piYJr~ z6ms#zWVm&Qz>uhZMQ8zWP{Z#-`YN-Vz2~-27>xC($$o$ylzh8o;BSFHC z+iZEK2rOCcl}r~e_gHbrWZdk)cbLLGEYBG=JX!r4sD4Mi1aYrCHIpQlzNVSQb;&LN zCvJw@Cm7G)cza(nv)wRacaJGuhMCR5nd)tg)zUr>!#I8Xu;8gay#!1a!X2KMmHxaY z2k!h0Qn}ZlWIy>COs&!L(mrQBahMV9sb%$)7Ax;ap_LL{ixqhcTJB?k0@7RthBQ2OncO*A#dD%-<)p#4bkkMqv1l`(X3>L zC(gH()AkTvYFL&^Nk2V(o2QRCE3<>DhH<(Dk0#3|bUeNsj>qf!j}#(k{E$SyHiTE= zYDIk*ZX63LM7_;OmeqQa~w~{(^K9MtlNO0=tm4#p@idxtwEu}$#i6U0Lln~ z=KX~r=rfpwRE@J<%#QR;jQM8Sl|Z;^wEQ6HSRl-(u}Xkh;!$*!zvqBrVfv0nmP z&=GF;Z1dNHtPon-^JSWPT0BPC7H8%BC5vH=ZP5 zn~gB|V2T}UA{m}wpQCJMz;4%2J6MR;)IddZx|2^GXtOq@(ByJm00u~0j`LJ0u!-%d z>&&lsw---EMUzEIsuVM1d(o$*K4mAMLV8e)$exsmFIFwSR;bJguuALFf$#7@o>r^v&;Wvf>(ms2b06dab*?upt%{V z$Yq)?IZE8SO9tj*P`%Xix>}pAF4D*I$J>vcYVHF#+XjO^dD*dlg>aw>ztn{dHpkH5 zctmZZ*%Ocm4??dN8{Zf8m!RNbZlA)ob*{V+m+qPRA3G4%r0P52U?+UA+i%_p{6K7g zQ%o+`(F9WJu*yGSDL9wu9?;^&g-y(QWJ98DOM~qk{a%XcGjBPBNl&+}xu=^c30bA= z(5cE(jPm1Wh=&-Zx0!0i0zaNMy;7bPcW>ZmH#WMPIu{t!X|!E2fE->46`hfpS^og2V0ohwDwr))fHF#$#t7uTAUnw<$Z%F|s1uIS3=6 z5_r4y@?$1}T;BeQ!w<)OqPF1eV?Jb8l5e+iy+z=?!aS=nKmp3N%nc8qR#1$B^? ztCdnySIkBpc9`aj0xcuFbGs%9BnMG*)9|nqJR3L&90j%Hq{Nq42qZ zq3V-RfrTS0Q8XFq7DlG1duL*gFy2(1Jv*uKv#LR>@gu-%q=2e}R#Fw^h%F??#^_A< zSPgu@-yqd3q23;Jco-{{3@L<>`n|H6KZ(Q>O=qlv;yTH7nbdAb)JLw+(*XIiO*LqQ zh%o{Yc`ZfHxrk&knLkA1Ayy%~eOo>?hox&hK4)lRv*)LbC}VX+parV!{_*sU*`)Mi zkhz1WL$axatf>0lK_AZp4jJDF@!d(CMlDy~Wuun}%S-}ExD~2jA@oHYD+;_N_bU5Y zACAZ4%jNCQnp8WahJRulbrv}S{HR>!;v(%I7Ik8J}y2S3pJ;d zDbC;Sl!tu=h1AfSn;CEAmL`R0qlAZWv8ZrtUvIPSgnhb?kV1%iw$g4bX`pnARAwJ0 z7W+hKVp;>cuBH_l2Tvlu3!J*TmChLCHq-_ zy#J&j-RSLp>b0a-U5CxT?S`9hBHr&(_J((kCiq%j*>PqMEiy$&E`XVtPe%JX30K z)f|#vNM&9}sS+CB+Xr*#7#*h21LRYbHpUSqd`GBc{Yorx(5YPpxMrJsdStoT_cTd( zpV2*W8nWHl5jT9cL&<#MZ<%~HeWVa;oy-XnZUOVg$t!xh^x(?@ zt(-Q78Lw;*D3Q0`Ie9&#OpIHa*4w*$70yo-u4Dt=HfjAh!*I+|;vYXz@$|rvr5NLx z2Wz?B+)@iphvU8cmW+kY1fKZ8#M2haXVb|tE${#X&Qc8_ct@pAnPQ~RY9KkUm zVzQM{2HIy-OWXQZJ*(+qn~v>m12!$~^YV@BYT`j5 zQ+8}dBPdiMKAxUlm(z9ocyo;^Ec8>64aP+E{jZ0^cs##c-hOK79&{fbcUHv?k}c7t z%!E+~(*ilG$22|u>uQS!u)1#j@MA~Ajp+tl>g-JM88up{I?N}hc$E5)#105GG*HVw zA_8JjRMASaa6F()hD6(MR%cO9-;-q!9;>LtO!|y8vvJxxdEvd%MRJjT?#2*sQv75? zT7m4$Eu)&x(09$JqFCX^3jA6pDbbU zn{|X?t9&$=oSwl-yP3Ab?*l4phe#p2rqCQLB;AM`=o@lGCbVhIgNt41|2-40ocj$) z%I$OJ?cRO^z91kuKP0Y0uSU|mowJ;hb$ppVet}9#XCrcl=Bo+*o4+`W=VhtLEv@|N<&WEC^Rov0Q|=CZs0DU_LTT$4md-cL^DX;XyCH%N1))R6 zYJ4ir)=T^RWT^!e0UwKc$5GMcfn)2lQptB&>{HlIgH%f+hIuiI=!R1hgsa+4dYw2Z z=znwxD#A4hK35lccAja;J7v9HS;&o|*q{QhL$d67))ijpR7L)`C!}{b1Wp3y+RCE4 zVA&xJr9#s`Wbw7_;(^WMkX)&EslUl`Zs{d`F__WEmO~R`AezAF`)hNwKs!K(vI02C zJhgfejQF8AQPPlx#-AUC-Xu5##O|6Dv&$uS(+|CMKh=j+|pYzSvs@q;kKK67>~=rTz>!ake{i8 zdSr@FTZYRRD)C^RGx2x%`#5If=Ii@!a5{`5B^o%DtZtyj00U%TK;`_xVR(97b``%5 zO9eO`@&QiMkPEbk^bpF1>s(QHF>u>x246uYFam9T9+%)3u0fWSUO-uv8P)}4wS{V6C+2LTrMdrw#!C_NK$T=Nk^COj8yh;v-yr(!H4{z^p$K$Y5u*ci` zQnt+pPM>DhgZoTAZ$ccuMbFG*k0IjI+{HpJnj2NSQ@X>l2oAbn zKQ~I!@c25(-EXZLbK-UkmLY4J7qtm=mJn>cy69|#9%($uJ7yDa_YtWkrIsgXhzO*(Tfa>Ma?IE>fpuX&z>U%7*AK)MMb^kZlK#`)Xz{V(!X zQ2(X$?!e3nX$)x}YOY`3bScBetjP91x$n(NnPsWC&eO+X_1jo~&0w|lS2dGGnv7xp z;JH&iKh5)Ho^QkchAS5&rP1n9rHSf-jp|=QNFQxu2j{B}ozuhlwH3~bdRxKST%j~L zurowasF$_S;%>i%eC*myx=eR`Qq?LU)I`RzP^L?$Q_AS48e3<}WsZS}2XdM|3 z9t5c@Ptzh8390DsC>fD#hMlBirYf3gjoi+oq1*guZs%~=j5MeD;<{Doq$y&WWP4UK z!CL+-HF4gFar=4^-0$r1N8J&^%mUY!8;>jL_~RFbp*RU*=Mzyt?eb(<`z%n{aD2ky zuq+irUMdbri~*CFP8NR`!mAS2cbE?2vQ!*D-u|qfxPl2aJ%e<8(#Fm#v zRO2plq_()|_N!`9EJk6btv6|yz_e~45n6T%OQdfZ@;`O@hoZ^hK4Kp*FHy@mP;tvN&>-fegCye z!Uq?mfL3bMNr*xAdpa(i_?$mZG$tC{7E}0TPIl7565i<%j@jE#YmBs9>;%HVrza+? z{TI!jD&wOfbw&0GY^2_9@W7yNaHKPMB4BC)vFe{WXwck%iXlFWa8C@1F$UPPLfTyo z9m|ny^c@tuojO_Net_CwnntEhOlUKCsIL+W5*Ayy7?3Cyew}5p>iGgOUdEvP6~9An z04;^K`3|&b34uTnDuhHcXh)A!XQ|eyfB|O1mu-}qMGYGt0-;vz&d=Q?-e;rGd+Y|j zg=`wE!)uV0&>6|m*d`+F8+VBOp0@L~r#Zw3%Adcm5)ykw|b^jMc~OqzjBM=CM@I z2$))pg&*A7dbo7IT@{E}pKDIv&Lc#$_lGO0bU`jGwVm|NCam=dP5uXZq`pu4Wgodx zK^0mMXFXDQ6atzQ1AXdH3Ds1Wl&TeO_gsjA@45!FgUwLdcNk=3XaMJ>cOJ96`)!@< zMbEms@d-xr-hbx!^v8VtNb@yA6EqlGsBNNYL{-$t3WMcXo|d1O)({pYUK9R-)lQJH z-_IWZT&m)G(o~f862fsZbme7^yFD+f#r6G1Ey*g7*EKpUT~)fjqTS;-o~GM%&aChu zc?Rc@diq_opaPmO-jq5O)QVrDU7^!FusW$x+BZ6ryIJ3acZV444v>&MQ&_fTjBXY} zz+!3~ow`Vax-il^8}jZ!im4;mrj1Zbte!@??>2?G>t0|~d1 zhMH1Wr(ilLut8&#*YY#zjY^yan_8cy{u1v;rIHpY((+W@M4=v^ZfDmq0ipMEg%VpR zQIKT23;lvc2i)e?_=!wrhqJq#!r$Wsyipyb^76{}c9-cE#)mLe%2dm_qnN4$!H7xX zyp2EXOh34eu*T>y1X7|GO*| zaNpdY zyr&fFvjdwC80TcXi&=L$hD_*AZ|M6bHKA|A0n#1dfsZ!b_oiP=ymR^)z3};93)6fB z?AJE-=7QmlV(cM04fnl3ade4y?M72}0xk#-|EAesdlS*0Y%jC!K`Zy?Wi2*OsLIGg z+RZ|PyXZ~l7q%BWj5t2u-v6XSW}2`B@WFb9VK|;%S0l|2#0L&-iMEpL4u0vvU*`Eb zO&>i5c0;`^LI(Kh?CI%`Oh>uA1yrNGAJLzyufXx)JRDA!jBixO6(=NgCkW{ALi2`T zIE+(np93p(|CPZ75{J}hb?EUi*Px_YJIl^8-*R$f2SOLd6Wx~HDZ=|6$Z_~h&v8>T z(y-qYjr8mdlMuY10?JO+wgRy*u0s$RsS3irxf#(G?F(yjlM{1)_ zIE+~%etr9IvO1!|HK{4ve6I4@*yw$Ci)E6RaY#m)?%Mn8W9H0%KHY8~ zQ!#E`s5yY0ueX52nCt`BxrreEsvKK3Va>7w%RcHp-x{U()tlxbx2k9sdIx zf8GawzxtE?O$PpcA^rsIc=(Nf(_1~Bi$=R05DIA41|1IB%5*OhgtM6dI* zk-z@-$N@e-QmN60dP|=Q;mpjhp4P{4b-z`~NaRJn#R_3Y%Q!Pq*vG?RusCNpch%X{rM=TN30Drf8`6 z_7;6e4a6)W6vV7is2y?=QH-=?;%gGq=Ld?Z2L)TQ=yg~{*@J^c!!1auroEP=X==JH zH9pPkxS&kRs_joY2C2$(FGA3Ji%z;LKZh7M7hXeE+Zsg*^N@F^z;#qcSW;aOJm*w& zF?-+~0(r&8jKOVXSIwvm58P>H2$FD)tXL z>z9r;*31$~t6YqowhE19r3JX_}{rO_$&uGyE~0j_2hwFP{kYp~##Nm)RaH zHpMe4URRjqC*}0Z86*kAC}G(`g|l7~ZHnRL8I# zC0hhu)Np1>%w`tF=HZctRD6I^Cv{8p%$hIE1wZ;@iq7$U|A&CtgLhbd{ zFdRNp>H%abnF_%JP*Y(iD5p~{mLsP5VlYwJ!F~6Om@zIzKA|-!i64q)0}I5Os%KJ% zrHBiF=1sW+y^Do1Hhs>l0zr-XZ~Q}l#(G!P&s}@os7){kqhn6I-v}{F>WmiIy5c}o z#4G-M`nFUaW@Dq|e2@C7aKEFkktL^ZNW*mbjfA`HZj~9|+5W7-R8Q}}|CjSXgLfop zw~8>wq~#0a_#MS3gKLlH@0@O#j#jNjw>EFkyva)HgU(Kis{zN;%P@?q@K5;9;83Z7 zOgfHeZ4_6JrSq~>Tt7b6T0R5{G#!d;xO5#xZpSj7p2p+(_VJcUhhh|>Su0#^J-i=# zVMa0~CkUgmXJ=3}4=Kh6ZA2%FObNa9z1eMlRjzr5j^>MwYC1VSpsfUL$+4`2h`meI zV~|zQ5g$D1&S`mqL%Xw{EOQ!~V$cx0Vh^GY^wdZfLoU4Ha_a$PkM5QrrC_qRIhKCLHBiF6`OcZg?%zgj>o5A9B{D}sfmm|sJ!4D=c8m@c72Ig$pai{a^u!fq$ z8`0_yp-R1d7Pp_E(Hm&djD)Evy>xzw^l#_ZTBL@-;S!jV<25uxxzc;f%0@@QkSVYj zVCkF8ZpIOF1Oh98E@`Ue?m2^)vt@%e;U3@b|v03Bcov~xOhPf|FC)>FkfGOH1qoa&|%V3O2Z*WH$C z!)Bp>&>;U^eg}O5Q2buc2P3Wqp=_vj?m2P45)w?_4qNDsqPpVY0>Dq+H1*W3YOgT za-Zb7XNJ(g(D(@|4rd4MGHTd#iya3jdS5@2KTyJPtk(~2AiiYviS4K^f*WiF>tM=R zS&NV@=zzvUF#^;GAQ$pmP?AQ@ zA@S_H1)PCJ_KBCacNm|qm!GTfFA#z^XF0me03Ul@9R3f(c%HL*@M?!En_%Ie5i$pr z+>h)?aeP{8;@j%+KXm$wv}?psXIH5CLYp(_zEorny!W5R>Dxa!eb`}a0Dx+;y)0TU z%a`)5LfM|`=vudm_4I3y;m#y^I*h06foDPR5^pI-F?2{yJ;PrQT?*=+)N`n@kD%1- z)anYquyLehH^sek-+|1HQTUk8<=vi{?t{K$1UbSSfjt&pMe)6?F{NO>#1QK0>EY<^ zliGjJfewUL8QDMUt?Ng`eN!hCJ@w`xEoR>p(mebpneI9%wqX3%otxQmND+-l=f|x3D%4A!0VySJ|1egW=)$N=~fl-O^Q}1)vZc|LtTB*^Tpb z`}lE>eEA6xaecWVJK>*KdG{^+U}QkE-D>QQTlU01J>4=1WJ;f~f)UL;c6X#~(tOHG z#jm_nC|-VVl#A534Q7+%@%*y<+7@c=eH!D-3}p1-4VONdltP^{;5UIP4l(~JX$z~n z6&hQ48tcZFC&}z)TR^!&uf@W@ajs*t>2reSYMnnzn;{o8-@z9YvaFf+gX&I@Z;5FX@tQZV(llFy81nLzSGT;1|fOkD(28Vh(Ja|MtxtF zf;e5@RZHih&Sh4Z5>zZwkLQ?~B+F9q2JhQ$=L7u0>PWdcg{&|X4sLMv{bbw9$nDzE`y>S zJ~maADRtJCds2!3*_%d^UQS3i)$@y~#~Z-b=SNq8j&)QhwYu7cKn62rM{sgY9ff*y zIqVR>NH^Q^XRnTJ5%|Dv+{;QGWPSdk7h}hW+c?I4@CJ6;)bwaCa=&7;ApoK}+S)j7tJ<>l?~eOOw&0jqBYQfSS5jG{|iu#!Z(W zd2`nokP{u)h$-Z#n{sb4Bm&;j-R5Ej9Y`_LusSI&)beln$P-<&4vkwat#aO`Fa6o-{em z2l`LLELG2(iyVy#{`qXcN!!OD`CayoIziqegk$MCB>3)bCKM4H27?Gx*re~B*BvHm zZVYWAZ^b_}KypC(7);Eu7<*(ovX<3mUf$MZjSVw@rdvCpYdpY%KR{j46= zV5+RyOC66DJMS)_%dJt%Kab~c7&4QDjxX=?tDyD+DJto+vC(M_Lj1We6>=-0wb2JH zNZdjbvoGlJ{F;v>n?||3#=85gvE5QL+;BT95T9P>>uMprzRJMr{=AX~vp3Z+9+o!y zFbo*krSrol1d??!KsYUl-aeX`q?>%-ShQRZXot!qT#;mnK~vgNo|fRst3# z1tHf4e;%6!h}V9Tz8d8_nnn;0pN=H~2n!89Cdo}#j4xWVGV3)Ne3n(0mF0~f6Dei~ zS_5hnmjwb;fDti!iY^RX2;HPlh7QUTivL7I8@Czg(DIm_lPXh)H>1me_eoGHvO&^d zrd|>cD+xuVtXFTLVtgV)`ps%GeFE2lkzm`gk=m#vh-6dq#SMDuD zOO?4I`wMl(L&i1hK1ltj*iGb9Cq!AE=5lxXi)|elB)`w_03^M+>et{mv-Qql)5$6b zRu>hO(pfo8dq5hzkA100qC;d&p#|!9t6qwxtc?-(H5M{#cVR~$&q6iYq1%!=Wtc8T z^|hheAeD6Qg$->bi4tQv+v{!DsqKCOMEBO&IXV%Qjk?1dI{rRdD^w?gacb8ZM_1#a zt*oOEB6U&jO3xpAW56Sa{ww>vusbF=b~(kXAmmoLQQ1xp*~|{;YLQcVr%v{k+&JlY z{D$n{^DAKWwx2PFp`9{O?&8waWX)d2H!gqum-Rw59Wy0~83;QD4NqJ;-*YAlzyGzV zIl@oT)`-1L^Fr$Wg%j}*Vu#QQCiuM~-edfFKJ(mOG-R97Z z22^QKR3Z=|uMomrRd+vOoJdqm3JiAMv04S#ROiU4^|Bq-kWz>0phpGoyUqKNXk?gH zh$p&rcn7NTyg}^c?M{IxC4!?5W90w_i;=b4<(NO;xX;5uNzogHRs--L?-e{o@y$BDclEV z?=0#R#;I3z(sz~-^;g_Ji=Nl9|3-}%aI22N!xYtNr{L-uCC~mGe~>OiEu2piHC7va zw8K3_Hlx&TdOs%`o00)Kt+8Lu92cGtsDs+bsV;371URekGoUxz-euB6gOWJFY7(1@ zu{|A|p!O~-k+OawNy!oU`$d{PJ&NoDwsq9Ra4viWis7N>E62dsawHm5x-)Fc5*vKZ zOD!XYoDxE&$opxvFx6a(;!8abhb-^DE=?=vKsu|WUr#@9I`;ii9md1BCH{Rl^?gs+ z0T~gxyx?4)zum6Cch6;7ig`#@M4J^@2X@F2S!te^Pb7K8U7n#Ez!$aVe&39z^HRB& z7b(ij7H&f@%O2XA>n3xhowITz%@Crkf$d<~687Nb!K7zdFoXq%8KY?$0;849itc?= z5NCJE$pzrqPs;f6_ac+G7LnIB8;$e{c^6Kz?cGYvHUuGbDh?g*@uA@)BBcUBxnpi4 zU{1)<1fRwVajYzfseR&kN6fE3&SehKuenL=(hSN(YNT-8^ab~ICIIRo@Kapo^QVz7NzI+ z`n{J=7|-lF3~9dpQoqPZlZTL5&0@{MhY#68dTCmJ$=*La=mTteM5|@D@>)mjkZdfdo`No?yNIyO_E zhvW<#7@o4|2txK#xO!CL)DmYArCp5}DcBNpvH{G}Sq0fj1MAFj`bxL(Q3tGk50@C4DoJKT57GMs((|gVt{XNeW7eO*q29ICVF=am(wL zJ(fxK&Tb?peF{UT)`-@tE+y*JDOMu%n_9Oa1$*wb>)Hvq^lGHT=@r8vH?3WB2+>7B zc1rNaHI6w8DwBnGd;?B%<79q-8(!G>IqOP}&r>#gn^Sl$A`9EI_cRiFST*8VJ$Olu zA?7%SP096n@xx&}y<{l-@_TjJh(elRe%!g*>o-p?f28>~UEh59#yv00?XZSUWHwWn znd|loj*3M|bgt$*`WrrnW4q=z87$E%Pqm0Q&bI?Nc=7n65Uw3nD$0RC1Z^*U*{)h|`j{k&Nofn4 zNN}PUjfR9jvkMbP1)`Y^@56NE<2TDab=C4+H_D;6TXzao8#`ZycQXuA)H_lDJJ%$3 zzyqV$2mxCe9Q;=-q+`Cb)r-2{!^22Wp&NE5*(}^jv|)p=Eu=n1!?@JME$doWa@T;t z)ajvvs~z$2`9J5|g`2tPo>%N-7_4?1J}?m|HH52gM&?leFvKESW*~y|YHG196{nZO z@**Fb#gapJ-P|6%*veApmbN#e*zZ5A%0v$59eAQ%VXj6XPk&sOUBzwl=7;|g!NB3n zSq3$#j*(CF(r9rbVnesB0KWYQsu+uIR4uA%vg%aD>dfSX2+M#sPVlD%D)vZj&YAp! zNl`jDpd%@1+0l1HUTyURZ8$=1np#JitPvufw`Y7Rs_Qi!eS@~Kr21@AjliiaD5;Qf z(F)9o05YToa45`!O*h=kfXxPzw$0gzSVRgEB=59Ta060B9DWo9$GxFi4#Fj07coWv z4#Wlkv3+kf`l%fd?Kn}ry^14`%-E0;{KNyDBei{JFA>>(W*E=U7>3K`m#gH? zhIS`kptsfDI*HTMx0G|X-jp;Bzx9Bs3Pnk%^J(_?Vxc=l!~u}{-Y0B?ckCxXh^)vd z6&r72Bw5GJw}_1$P{bA}`dw1StgVoro*LM0lwaC4&NaESG3bM|5Y`tQX?Q~H{{gj{v1ms<4jnNRQMxi+Cd1uZbcG{VI}J7qq%%@X*RiL530*HQYYt@)`%zXeTi{j zD*oGRF9ilS`@7nLdPZP26FHB^r!4%NZn(#zfja%MrPr*!ps%<0pUZi1I{WT~7HvE^ zR!z%rdil@k_HoPjhOkA*_)E_Ae=pE*RN*+)_TuFofp_dI1qF1}!>EFG7I?{1M`j_~ znO&X=5TY@Yb(wUMbC=lda9AX1Fop zYY(T8i?xH*FHyv{wIZTLS1pRtbDAro&jz9`C|@=c)+8rKaBRqxsvM*WtNolk!1x2T&)o5dZ{K?vf=G$E85uE`L>HE|`&-kR!BrbY|1*etbBrz8P<;Bfc0X z)S;L9wowAN|>xp*R=wXE&(M$9-qE1hjGm@wE_lPl;;jYx>JY9 zs5y+QZ^rwd>zN)Pl$#1lAprbHm7%fSv4>-3*WEs{ksctTSU6fyWPhxG8TH9>V&*EG zOYP=7MxCZ6y0bi5{Y<*hLBa%FdzpZvyH)hhM2^PdhlJ4&of?V=K~!m+sU*AdT)L$` zb)rcd*1aOmxc|I*D+M}VD8S~KaAx01EP?3zAW)2|sXH-B?o;{T$JjzJ@G*<9w&(=h z`KA|=8b=ZyXRRVhZDPAAfQZ+kB8=Q+;c_Lp#)rDew1^#xmeaY8cD?T@g46C04^DMS zeGt&Ox?f;L!vkr!TkLHZAU0uL%$)(mNtUOY3|J6W+t#U#&&z+Vm!C}zKs=zp+c&*8 zIhI!6-mWv!faSzeCg>T1Va^I`j(6rs~V_2f0bTUSYWY|Gjq z0#}GkyICpokIV#WVT8aM03ut&LFX*Xj3_aSvRvk|j1()({`{<~G(= zU_0Q;6-OC)LYxFB7~C98@KY^KmV&9z^eSdZ9jAv580+;p7r)}rfl1f9Zts_ zVE8)+(_o+Z-LBJ)T+X?MJhiRT}*LmP=8aeu<5%O0exf;eVC$sUcWJ5Ub zykp1_#wNWqjUG>~Pr_<9>FL4S@^7gRLUcF!r=Ulf!xzt2(@3#r>}~7AfNscT=<}5z zj8C@U_}4GCw~zDaV?A?-VI^(Pl^Av>Jtr-{ML@a}VI7x=dxrJtp7k@@9Buq7x6aon z(`x+j&?b+a%^bh;i1y?lSeEayz5?J6paF7xozSz1{9M6B; zEg|9}`r$rXtstHHwt%eBnUo>C zm4(Q_x<#v8u0oL_#mLgIZKl==-ZAIw%rHn)=tPQlve5PKF(Z*dE5bl~qaI{p6idtv zPn8{-IfZHXMAvD@04`2O^hI`|s78f4=Z`sWzK|Wrr{ktb<@V>GUcPXL2tiyTiWt@P8NFbg}dkz zulW{_y<^zpN9Sw;Ng`3={#U|&gnjx_^WJxks~-s43X}OLW2sRj_nqDPXXDdm3T2%` zN_{Kk&gio34nEst@XpbaU3IZnb+aYW^muEsgX+7B-WTPOon1-qMVd41E}e{!(Vr+b z+a8(X;?sNe-}MJ~Cy5~@mH(6OeJP5E)64DSPu4JHe0qboFLK`C+BIP|Sa$Hy4ApVL zhvU;frt6x=+5kbH@9EYz8Gz%{A1kNrXOkR^`>(-pdXG7kYFTf77c{F4wqjDeulLc8 z=hx-5T;G4XIFHA>ra1#C^~(dz?#MYNr5boDZW-{jHj<5_vPss8KBXxsNU4opEy0*6 zNVJtnjl`5%$2Rkx_YJpamI+-~8-0mPVXuzoafdO$-MkL7Yk4#V#nDv4LexljSuIMW zkKQAB6GaFdT$Y`H+a<>09z~G%@EC9405ZhGI`yMOj_MhQBqdO@k(ygkB1GcZX3Vpt z_FWC{zFy_1LrmU(F`2mrJ}EZlyUMWZftpxZ&j#B#v$xWHd1NT_7_eyLb=avy2soG2 zB$-oj2O)iI^2t@}J7vswO{H?sfrUbd7)dz`HD`9cjM+B>hwJ-aG9-8T-lqQZ5#`$9 zAI4Lj?0u|RP&%S2Y_tlyiU88p$GjO|)#^+8>_8HTL?-Ypgl1Cjl3`gYp8wCVA&wX8JbmGEf*8Cw&~ZG{jWhJl^B$LInn8ZL z-g<)@ik2lsi|ddEPp!6%n`}^~$b;SuvDMZJJ`lMlcc${#3Wx)TKB3$7Q`v;{q`VZZ zKPuADkgj^A6iT=Dt1!aUU4?=b#~^#VgmhPw=@qe&dd>pND7H+W6yt|;aqgp+Jsi{a z#&dEA6GAC&Bz-|2YutxA;k`2_kN zUI&%Si@a~6{{hhIYNaa_S;ADi*)i^A>BO&Z|7|oOO$sE#!&yzaAmKDi+ncjsK33lh za|l9Ies|f7v!bLiQy0BNI6%6+bTb)xI z4GVGiKt1&`kyOJDO2(q(zaIm7cA;XBw^ChZPA$S?Zy6e6a;q3Wk^YJP&399 zHTFr`F}czSZX1SST*?`8`^>3p*5~yx+_w(UVgF{UzZxz(ROarV$Kz0X`ZB;!axMWS}?CQn-^XRlyV)5D)}R=ve2_0>S-2D%`>p zLuWT+ukzs})|0}nX>~QEzyzCBOK@LDYBhph@keopV8=m-cSc*(ZMF9=G`D5O2tL~n zvU)nhHV<8R9BL}RZ?G8?6eDxPpnr%}p0y7USF^NbduSXW)Qgi$R_M(@fNp=|=zquf#4N-#K>(?cOO?dK`{wQ(MK>A&il32mZHF zj;7C1C+B(wEtE-0gbodhQEW|XPnJ5x!ko-D$A2tsFQ-c}U`5!=HL;RPz}lM9!%|qu zX>Xq)`3Z(Bh(wMsWCHb@9M9h~$#{7Y4JI~ps_NgH(}ha)W$t{(r|a8~S}jm9Ab3)K zHJpX#=H&eEjHh2OKY5!{H9+0(CGL~E&8$DUO3iKKbV8@b3ldsWa1ILs1rN= zB1v}J#F&5ppo^TNFm6Wauw-pOF<`Kk50S+>){&IA=wNZFlbI(XNODx;RVA18m!rd47%J#F?HUP*||653FGNM zwsIzeE8&V&P8m{!iHshWqq|dfDE%2mCmN20fchbXdoAVcb@|&$nV4%bw&A*EkEsD2 zb<9@V`lURN=RekHt67g8jU@@7$UY$CxNJ#s{2;Kg(mca>f;BO z+FET*7rv_Zj(q@!&efT?Sqt zK$sDL-LO`sblQ7)`O!#!62}rQEy9-HewL&c6&D1ybFQL-uz27o2#xQL{oWro;WF( zKlDnBds>R?<@f(h<_W-{>sGcR;O0=5HfJAu>F^C4Z4 zuBYQDjR~)}G|%FGYyC&QYU8-PXW1f=PcN@$ybR(R#uOUC76d)5+Hg4EJLofv#j>Gs|FyH80h}M*`O;=jQ(BtW4zP_#El+=*M!mSSfrTU%W zn8$y4R*smXOB-J(D#V(__DN~m$x4_;phaFrUB~WELaDYZ*e@Gv*AXz0ohr8 zSfjT~c6S=~|FXp8QZl(w`rphXS(C`?chlxNy0pdsEcRHf37P7XDlYxQ}lYXEwHz_ZKkW5bK*8|Rk?%e6{_Wmz2_zcMvg44Vj1PA&3oRB8m}wL z4K@^MC3Xj1Bs-HKji^+fBs@f49p)R_3QbO4kx9*dFN}0h_XHkBrt-b09J0a%^U0@D z*Glw6q1Cx)pPJ*bRZ;{}JN_QV(IKP7)uC{jBhv&f@~AK+%oDq8>@qR#__`G7jQOU1 zL(hP`LL*W4Pd;RjAI4!SJ)PvCYE5uL;F$GPPvbz4l zkfXb&>#vnE;noVIga;f0)LQ@OFU8Jya z#EPu?SJ-@T57}c&KmnUBAkrm*4>MYJ37Vy;1I{)X-gW55;35I1=y0B#-p+a_Mv0*- zQ*u-%2H)%CZ7eLf!i*i68g&hfgR^I7hTN~FjU?ibL&lYA$!Su*m?BB_UL#}jS_30H zqZBb3-xpm-x7cC0(YWm}&fP9!lX0(AA!D{BOWH=`bw>#oW(Y0$m;baQdpa9K8Q%L$ zFDqp%U$ZC6x7c!qw(LR6hgYS6k=9FFr+ojKjfIgsj#mN&os zb?JPcr^`DxgFN14*@q}e_a~HrzoY>0F$w%$^GrS z)#;xRC(&AG6N!$uq1b?}_#?abx28VXw&M}JEB;fP3qQ;{Y!@6>yEGTC^c)(6RPpp!<@FtgnG z9<$loC65Ow@)z`2>6&~bjpzCz$Cv5$KF^m`IY%EW3#Fvk-GZ7Tv!(V8=jZ=iYT~k5 zsPHll6TLE1`BK{*ymh|W`dI^K0{z1>@f4}Ihjtv#Shgm)%1bO*^Ge-Ut*1Uq-j@rM zWilMhNY8f}4n2e)SM08qHPvjxyS}PL;*(n@R<=#+j9#xwG!*B5)N|MWh8IXiM2(0^M9AM;+71 zW2eh$;M~^Pt*ZERpILNBkZv3nCGwDh-H%kNPklXAaQpwZn~s;*`~{J=!Oe!viEU6ytL2DUVgg#FusKpWQR0EIh%S- zGF$L)dYLZ2$Quh~!d}iXJUwmB^{mIk_%vTXX5QGO82iaba5dm=tnDuEQHE>pyi z1U8p^@vyv5GQ#P7&P`TG7r(JDyRI3gcwNbDE(2kxeJRaADfn2x%#;iRG4~#)lHAJ% zEl(DDKNmU*8jiX3w$NwPb@T|+ie`(zI`s;mw?#okP!;6p9@Axi8T= z+eI3^i;zzp-aB2@Og%9OflkhG>sW%eDXuKJ$2QUdi*lDy8B9O{~huS#SQrV*p@mSh5L$*@Z! zKV8;ArG~Y;6&ZIl8ujgtOCx`~6vI{JO*rj8^u^!+b!4*LdB9;_b&LtsEJ@`c$_;j3 zq4>cgxwKdZI0aUbU;U8a()HXZm6XI8zP%kUZ&>aC^8yi>W#d&&ClH6ut0xN7IAbLWNtG3=dT( z*zlKyV)B?|W?*}LIE#>uKNCMI&sA()Nkp(z>xw&;?G!q^fb9XpcpOeo)AgtMtHG}t zoj}{FYN&(L!{PkxmeaB~S$64@t4en{gY=d}*%v{XC@$#8k z7pE#)1*2h=>jQ?v(mpTeWG;%z!8r{7ABlAyRutFRWPv1ad}sfu==Qzkem)__hU|Um*eVtr23Np#uI(eup9$&~hV{h)qkSBylN<53xnN zPfqq|+__;A!K>kNFq;N$507aMmN;#ryeCTgPITHj69qn%vq@J%N3~G7RhG8HAyo@_ zGJB;?vh0h5q_DEZY1P=;s^*J=VVJy9uPKSVsjHO}u8L?#=aMhvc7KY?-gvz<{2F=J z4X|Cd2UBy$?RFoRzw70-yoW&=FPI&+Otk3T3}AOPGtzDs%->tC@>;u!u7&f$g56fzRD6fH)C>T8e8 zuG?+*-|iC{5W2ZARB*sd1!^Z8+s(tbalEGS-~by0Y)HOb8FDT}zr$1fcWf171=3(> zJArL{s0{$c_;Hf1B(66WV$_l*h#YKe9~MttY=`aXmh!PiDuO4>d58hs(!T@!2(=yMk^+z!R$}-aYf*B;UW)Q66{7?deAp|Mdo{ozHtA32i^s!Ty zMP;fCCsr6FWH%2($JLy0K$ixs3kag27NsyOKvVZ3`7OifkHI!Up*RfXD+W=XJEjAD z3=|A85r&%MFj^DO2H7RSKXf&eT%+cS8Hz2_ZmoBOVEqVk=TPI<8&ZRvY6GFkY^zcB zjsr!bAGhu>9ZU^Xg4ElW{A-FdTCQjL;Nrznn2kZhxRJzzCv5ijm;a}T@wG1o{MgvA zeDcTJ>>pxvy}bWDB@J2EXHm8n!A|9zl=}{;ecqjZT;BeNyn$H>S@XHc05oI#Md)3= zIguTn?)OWqXaHRP7c8^BSh-9^kEl*w*q^?QxBKn#KK;$;`$22xSpH*{4&KM5ejlEm z?i|iJ2U;@ zAn27iMTDBu(ZdiS7EV1k2B?~Fa=+4r%6tIph}3#L7-J&w)lki?I{`txFPy1Sxed%I zvUR=5HlP1?Fridsab#As>EYx_w}(R49kp4r6gbQzLfG+~wcvmy5DR0{(gxr>sR389 zs#QyFY19}9>iFc1)$wn`z*!SW`WS*b8Rf9n?8UO(?4KrAql*qX)=XE~H16|N+Z>+K zM*i|LK?Fm+#k`&d)&NUU`-HS4@4EBr0*!eqVu?U0E50;aGa$GT@4x@=d+dBenC;l9zTTwg1k9?Iboe3F+xtB(d)PkSZts`N zd+WqyEIZX*q;izzmUDA_{J*Z(^X2_L)$w?8r+$5)Cb`kEw5-!&2%0-0)G~+;O%dQW zzcd61pRQ_jPjwb`N69jxnnHpav}AVoCSSatjHPi;BCVkpomeQi_@ldcC01363#L(R zI$EYg1D%_gKWmpRtcnt2Kz(ih%7*N%>3(=ct&d=?|kmgbUh8(L1*&riHvk_0h-6%Gk%DMw97DS~$v z;AV2si~V!TV-(C`9zk|p0m_(DaI@X%DIRLFey))_GBC)96U4;T3TH5dY;AX^>)Y?N zH9>XU8`j+V`GFaJqxTMdk%<@YhHm!hS13iHXMD0|@7^Ue(X#_TiYe&HRivxx{+(Gd z+fTwyXf+#J<|l*wo@ixd!wmPg{K^)a&inhrx5GXcfnRdIZ}lGiaVOAhMH&sabNIt5 zK7fBkX5;_&ys8x>_5Z^cKl~lSiW5^T&d=lRo#R=P&~l|P79MMarm4z5avX1$-vP~j z>C;n?UvG%*^JbG=n{of>W&}RCPlw%U*zD4J!|@BR+-O5)7$x};JteqiysR=GVKcjZ zY(mN_*zO+_`h0z_3dn$eiDO}Z`hL5--X@MWA5{&u12wT;tl z_~K&VjfXVfQaH^p9PQ+4lsex(6PpscUKi+q4NO%dyk}xYJjm`e#jjkfy9U_$2C}mR z&mjSu!?&pizoHEUv{P+ZmSd%7PcC}XQt`TY&N0;Js2)8Mp#}z(wSKpMN*P<1SB0w4 zbdYxX2}r_t(su73HkCs5MLyC65BP=TPKH2yHau^i?$EVVK4E zpLn*Hy#r|0DEf4jOa`AfYomRyxx5<_0^vv-|+tUvPb zfO&`ehaYjdxnAGeP4p1F-9Z?aczcW#6Z*0-sOD^;8~Y{4YGP9mlu(;HY?at8rWpd` zDL5dWnD(htiJk!RG_Mw%?XjiVvJsWS*cQ<>Rl<3ZBwd5g&k*IP%6L`d7jW$F)RfvM2>>Z=4Xa+YW;rBv2U0v7U; z1XZ7t;mPU-;b@g@ z>-On#0X>+AxT-2-k`5>9XAK^#n=i2OM-ci=p0bYE%p3XSYGhT;#lnCR^_}ynO9tS= zZpJPx6;tGPC_YWWHo4V`Qxd^ZI7rFZen019z08qR%WE5iO~THr{UAhE8Q;mbSp6o$ z)iG$`R|yC&Q{Re!>x^UK`R$IegTB81nx#XH_bX6rVS(N^Ozrb_KgG+wq-dMLc$21% zygX$n`{pJ0IDPQ3@?&ZCg+WDQv?v8-0_X>L7D6pahFHfSOprl8QM9RE<6;T1B{KX9dAKJ1h1!8_Ft#{)S;xh zuhzDMGIsaWW~hgMsqru_{_Oe&x{}3ifkiM5hUg~rqVt2A2;cEq=>lZBe8Zq9xlFgy zm@+_Nt7k>k+i5vL6(Lr550qtSp*wSinR@I^%kmD8AXK?T5Xp#ehHl@Z;C;P zapPpCsIpG|S*Z|oPpfoOE?uDYhoGkh)iR@Ru<3exzyyTNWQqkm|CiMGDC+&J2}N+P z=lq#^h0Jv9M#CUd8!=99l(nvUHB6eq8>5c_F&`2ZCSljG&=$O<*RgI#!6e8~ZVX?C zs$X=6HzPNPG&X@9?C^6QJX{4wy`G0_IXJOsMHJ?CP<-5*yyFjD?hfIkHaubvTWf%wAHgT zp*Hn=mSMz>HJ0UTN+KWaYXhNa#zXKQX}Wlu&0%|ZzP$YtG!xzMP&zUy$(Nm`otyps z^b|{cj<#Vi_AX&CNf&go?&{#{4EwEFr(<&|HZhq`!R1bBel=Ll zxFvN2^n*CJM}?#z+N~7$z)YYlk4LT=0Vf>egV|fJK1uX&?7(2}tX!;^A!ei0K0drpbC))Bj z*5nj;YE)eaO}_`pz?~E>yEF&W3ItS(x6E*(Q%hkn0f>LS*`8uCO8!c-6(>|cO(FKz zv2jI&mR!Bb)#&h)Acd4sTRqf$iwn_}=k)QSxQGYY9KR*Mi_2@_6S~E^z$8Nn)`OY% zCDYd9BV+q~ehr!tAj@_(YtjHfRh*~a9*)oP0wp}1`3F>xgeBG6HX&A@CwzKJqt>^? z!3tO91ZKl|!iMt9z|mOt#;O&^V|PCiZ;gwGnO z-YHO>oHl-5PV-TIsgEMGXO#UBZdc3HNL}q!>YyAe6S{SBB%SSVZxiOd*Mh&-VfQ~N z6RwL}tln=l8p+O^Ea-E4`yiMc(JwUA%{R)&nIgB7h&gq>CE{4{akuq#A=Aq_nyit= z+uPDU16Fzw!pbz?eZ^*T8m7Hi?a)W-5gGLbEI6+Ihhkw!3(o%lpr?`V2L~-r7uhPX+6h5Uc!de|kub{CM8( zHU@Bu|ClDA+ZuVn0bD1s&C`f5N{0hjVwHzWMK z#M&WbZ zdh)rXpbAL!fm&|yL8KWr-jX|juio$%R|`zG#L0t(w?ou%j|CnsqS{sLyU2l;e}gyOe4eTRwihGJn=p zPrQrcEs0YuB@nbxV3`}DZHJTH^6^J(pSdKbfNkr};JDsxSU@bS@jIs6Zs=KI%S?18 zWURA>&E%q&h;ix-Wam~6i(9JhP}$9!{nN17Uta$k68enLaWKl4VNf)}Ae)Jzw?Ez9 zU&uS~;SSfV=dcvL-8Q-Cof9Q|*f@}77?-tTu!g+ZW|we7za|^8xWh_zxZUX%vr9tV z?M2$H_;)BdM|N;(5`5|N@3_LKmGDxRCg+G!V!}q!CK0;;(K6M93lnG}!dv>G-xXF0 zjIivc30e7=61R&S{S-R+3r22(y7aK&R>G>lr9)s_Ovy$p21#tR1{rmL0?msF zst8`NFJKr{DlXTWVtOyor_Q@;gO@0Fq}@<#Lo6 zl~$sH=nuQYX7?~&-X=SI*=W+FQuU%(DQ3{a?%}W7^=0PyEm1Os7bhxjpKPQ9d|14*z6wDlX^Rc9)oj58f~@EDH-xOB}2a1X?HcuUZQ!+ zUeH=e(?|&s6pg2w!7NXiPIEL@4?btJuoN=>ZwG>IV8Wn-FKIo6Qel?kLSS1aUQj>P zT)SGYWeZZM2(QUWQ6dGs@jDD3qj2Ah;!f{IpMcFy*(4htp{Ff2i;6qeD8?y%rBW&m z8m?a%*0f1h^&4amh?==u>uC?J_PaG%f5aVH6T!(A%x&6fWrCq)Iq`4zqgLKEOAmvhR>-ajQ1>N&%Otq6tMQ)-uW8ZR><*9j>)VuT1)KF^ha85L9yEkCO*?lFvkA4^ zGFS&(4)w9C-ohp-P90|I{@$nuAr3R{E`*D*aBMT9C{)+;;(Jv4pwdhs3!^je0o`vZ zGOPrL%D)4~ERG%F`RWKgw+*^SYvUA&*AZ0Af$OgW!pWuj1R?nH25;s>NzNAZxV z38KN+B$1W;Z6SLvJzgX>FwZLO2EcH3G}b#KLtR2~#$(FWLlcEA;4Iyy12rQchYiCG zv{^(F%@0~jB#$xJz&hGcQ=7BQ-j21?B2npt&4pLyVu9Tk0+W51>7a*?=cCTmYI^=B z+VV6^v)Pd~85d?K2{U_`SsKCUoNx2bHvrCXwk9;FxERcfpMKL!0Y_yGf zlTvmSu5UI95MyY?im_Cj#A+{tOSf6%kR6nw(Cb`KPwCJJi&M~Q4QSXJlK5qSf6s#^ z`zUy=OX%2Bv2h5)CxpADecx<1`^SlQ@t))NbfgzFRUo+d$Ldgt?cv*)1mfqcBJpn! zW-w_YyUPo1lR$h()<}vAhc!U@D;Bo9>-(Q2 z2xjeEqEMzg)>D&f>uHMnx)u*XvxO5veJ~(g>4PWh*ImkRxtsRciOTW@B-V0?J%v7~ z$o7eB5l>I9qKYdVTRJN{ip9;6BxRjwN{lrjA-9fz45#SL1qbK|R0OnSqj{`MWy`LC z!Snf{lLOmGQn0NGB|$Y8X6hEc6hk@}AX$Wh0ht{ZZ!^4|z(dHxv~e=l_<1xxP_7Px z4+-nYI3!yOo=@PDW7UpI&lOC-pp^y`r<~gKq8mo49JR_MS};b_z-n`grg=cM2GzCI&Bo#}X5#B-&}X;V z?j{_(v&RzHeS*Rd$0sq8r&3m(v72h3!s2aRp&rHa0o3Aypc%&s(W2c#TQP+#wxxCR z`_7ymOwacT@+N34_m+`ab^?JBL~4Uv&<8XA>H0czZO5Q^RG`h(V<$R7Y!Yhf7(bx_ z2ZP^`io?S9OAJM#BL=`a6Zc9?kn6kqg=bPZkOG%Uz&FjV=p(~d$5NLKtpM#+DTHbS zLZ^Rkh~zZDjd$~_o8s|_b2RqRj!X-pkcFr2-wA?!i6wsNpKmJl84O9yLgUW zWU}I$Dh|r5616Qvb>OhZD0y!gmx|5y;dXr`Z>4?~I3CWAA=~27R-lAF?@#yZ<$k{{ zaoenfO_pn_dtWq3S82U@7;jg`_L*oAM{V2FO1a;kViVo&ii3{5dzR$^TS0b~Ltr&S2C0iNg=1!Df)qfvU62OUU9Qw0N}C)zTtj zp>oOX6*P$?B>R3NY$+mac7Ak}6$URyhnzAy&JZOu6^;vzmsqV|gQorw8Ubm+@~;0! zO$X1GK6Oh8!Phj;W$C!ezz`h-`@^recOJIK>-qOQ2Wu0-k*!XVGf!!(`zOKPHXh_~ zOR=!mfL4od2z*`afhI-FZXGX9f6*Ubc zsM4jDeprGRg6;WD94}15n9f9%`qLOx*QoY#vd|b>gZcxy>amW2^!R?N)@2U12#!^j z*e3wZi7`%=>j#xLDqtK}KvtZI~#NKs==vPDqRAx@TUtj~%XYLixgP z7UzxREZVak%DptuA<7kocP{ig=(@&^5dtP{Id#zQuYY!_iQa`q?LiaNof*xc!gCbsZ)0IEn1!}}c?q8$ zP!A@sK$IjxTR%{ZeR3#oa1<0nTc(e$wxPZ=3y-6cN6<3ZjIBruSp-kgH#9Y>>;BS~ zkIf@YwW~0_VYhR#DtCH^>e8OWO!`o^Ksuy?CDS#IPR98Zp5yCzCTxiu%AsUi(iM7S zGaoH+a+Kt=%;bX*N|XcB1RVr*MFrJXu63z1)*IB}Jndl7Ikkk)++5XVO)PF?tG4W* zvA9)`)gRX(L%r3zTfK)Vk}5W>#nsT^fe`1If(tF1{pAt4$Ab*bGj-?oW0-RkZ|{FHXGw?zGQ*+OCFKP%gBjaB(*EYp$a~%5iRbXR zq;5IMYnNs+dlC7)W7z6`<)+hJ&htSoDp*;4&oEW#H& z#h6NathNUd+-(+oPz^h(-9pfu$t(?LmgXbt%>f&l#iFGoL-uNgqqx)vUigNN)(eFu zC=J@u!|A%yxTGNzjCC@sSZ1M4*;)j7XQU&YgVL2RygW1dFM9>`!*S6^@K9?gduV$% z6bU07_ol_+Od@!YF)KSlZ>yAU%|G<*)L{VsKLEfBN*Ih(LK;A*$~}Q*H|RA@t1}4G zR8fP!#p+?Hm*@uquYXO$v@vnp@8Ui&*xgzS{G zdO3C5-P1(gn1JI+#EddXK`9|>3?VbO<-pwSA7a;fdHbJa7q@sn=ev-mJuSWyaE#bR zu=jL5{~GUiA6um`fX_2eQtYCDn#dcE_uKWl!>FvN9m8aW$`oy}VY9V|th)kX1G9_zQ8DlG?i5jH*%a4X&#Dd>vign$N019 z$EV-Ll0DY1;-)h#l5ttM-LJ=oC&KC^MD(zL5?%y>poXik_UFu^SQGk(8h=7R1tNQ$ zrxjgQF(&OKPQ(+EAwoKjDZ7BOz~A(~!E{97CVIvuozr<$ArxaxL%cIKsg9-sGqt0& zZ>?J)>tY(9DsKi!IPv7c5<9KpdiLgARkouOLwT;v*thUJ*~!~B)P@GIZ(#;Ve0MO! z8PJjqLoeCE`kwd;hn{purZ26=kCoY*T~#~b0tgO1u^3&)X}Wpkx?;iHe~q@VwG2$1 z@9y-U@%l2}&jALz(gkWE__LZ}X0-+vDaY<9{&1@DFnuM_SQ+RR2?o;K9iHQ%QkCbJ zL-ktnWNp?MKzcTLLZ7$C+obyIQQyR5u8b97JbW;PuWa|vv5`+Q8K7AdjoRMDi)SQf z9Jc#d?X4&zrm{*6uBfiO;;*eF#NF|6yj||sGlCTbnbq5`J!#_*EBf{AZMQqc zKDw8dw@$`n6|0p_qlyL07)ax@iiDEJC>dQTOeH&ahGnZvErPv7M4hF}hGx~`qj+1< z2QP9x&d#kzZl7KlDT;7Al0r~GTseAX`efC8v_mAdIDm46#EVE_0>>7l>#osDGzpH? z=;_RKYB2<7S%T)8)%!!I^%?zy6y#D@Xa(+6`y8*bbEaFgZu7v3V^1yAK+v6ud3*Cz z!>jW^+*2jGg*Cl6>t4p?!cX5nZZ^ju*^~a% zgH)LlR^CTBST^F^9)HBP_kMHXST;u(Zh^6ehZFi7#@je~&e@C;ujasd3gzMSpZHng zQUSxivMnyqigxOg6XJP&jLrP@?Pn(f1RAiV04D5Se3E6x3v_)CldB0jyyrqmE;PjS z3Nws~%lS37+sD&$?L3tTY{HDJ4ue*RC#?f|Oe&cO(z2@+Ua|QdWSgi=x`0-lU$j&x zCC26XWbHMD+$y_t!@UlsNwuoG4Tn11y%7Xjts9?`PUpSwR;xfiQD{PDj7T&inmkyNe&`&~U%dj|*zOek@&* z9jcfSyRp}WtqrZS&+dnr(p4d*cBOvM*#0Ek?#=ADo80Yxn~2=Ea_nSR(Io@3_nq)$ zX06-kBbD^h{Lm&ndfZd|h0yKYd}AhK*{)x{msWP2HR;39LmM{~Ty?}cIoO6L6Jc#| z=d5==`J}!|1|!RJnuh>4En+{|0OsTyKtW|K4In)Zv3*Wbj^9(~yZXk1Gv#&zo7g_@ zA8+TM#REuKzF)cH7f1c1GEb%%XLNRRW*lIjeTe01Y~`QF@tUlkLx~G)A>z>Sg{>n6 z>{1xxH4Q2e&_szSS=z4^a=K#sjP367`d{l$tgaoO3GfrI{4h+$b)3^=daQY~5taL3 zD5I0}Yg}q>Nmw{+LhF=jSh%obL{b@Wu9?+NGU)sbT0e|Zj;6K_-jEiq~XyglAPsk;g>`!*;tB62sdKiMy zXqtwcZU8!(%U1qE)r<=}(T@-cz+p1A?X5t0Aj>|&r{Icd0DEUc9BToa)$Z7lby&&b zV^B9x-X5l+UGCx0?F9w4>vJ|d49H+61)xY1gs$3^wXH*qKCGb9a=>~;$NT;L?PU|2=-ob7 z4c+UnrEYdw{k61THj|r<|6}WK{XxB3c?En%bPA31o+}mc(+BXQbMd(q+5hI??Wr)F zbFLkmpTGVGbkL8kY5&jozx{0MMpFH^%+KD^diDJDu3q^dAAUA|(rAA5>kHq@&EXj- zZ~ONYUHZlI>pH*5G~@7ekL~k#@4zTud#_1Oy?dN|pU((G__selm(M98fR}g1a@KuN z+s$vGmB@S9AHKz{z~cM->51(o^~D~1|1=ES#4G0)I^~9sINzGFq9&<&{5VN#)8DK1 z4>;$@P=-+F#3LE^hliOIGm@6DX~L}AzS^Bg1W%wq=!}|VX2xJsUJE={!ggrFPvx(Q z)Xk_RjE`rv-mJ~Bj+NBrt-4IN+w)F1fXVM}o%)0K!K->m+Pwg^Q&yrR6=S8E`6(9w z#WhOX<*Dg*Z_BTO6ebC~3k{XM2K^vTs%R6=7fN?yo9(vTHZr?PtgY+Z2!rUGVQuS? z8fZ z<=50BcG^`)LMveql!9Z#-g!8jQY`HKYG&(Di!9_=aiYc#Rt?IK^x=QSk24cva*uEJ zG0G&#%6!RzpI&3Pe~h2yetSpU-(+TaerXdETflgTf$?CT&iG#5e(w*Du_vd54|PXw zwl+(CiMPJ&iI=TfPAn+aw~RfJV_*{B+;!FG@%B%wB7KERep6`vxLnkfy|Q~q9;f$n=whSBn4x{3r?FnLocl3eUp1`6 z@`zyA*`Y%zM2b=To~E6b-x?zu+FGRKPs{vE`+>s~1|*farY1J`mK$@}!8AAfV+usS zye8gr53CZZ7mWLDu`~%LKr~$5Ut_&L93O^ZfKM7Tk%T$$3NCqyw!+NWxc#35+%yeZ&m=$D{R~=^@Li^al z+NPlq*APBfBn6h)fx1<#P!Co(1S*ZR+nw~LH7|f>zl2Z+oyOyHoH=PWB+^cJtBy45 zvVsmU7yAsfaE9CsYQ3Quh$7vj`ApUOylOWCeUDyAKr4Mg2x<%6LA4~r#>P|U5SY-Y zWoh?m5Ys(m=Z@-rw}~~eBBcE~wu-f8%I#)0u1hs>%x;~?NiWu)q}iKbxwR5YA-2zP zo#50yTPrg6Bd`|VnLC`Wb1G-aJo@%t^>6UzMp`4oB0@wlCWvk2E{#lY@e_q6%fK|g zLn91fm7MG*vfw0DFC7sqPK)Z)pe<|xNXN+(aC11n{+gwMg0&k*#XE*!vq7xN z(=Ti{3H=zFE6CVH-|v^h=`nuW8avEk0lmo%9f2UU2<_L#EF@Q_g?)GeY&zSugM574 zjuh>)F@XoBLlb7$p0N)ovZ3dkw89oxvw$wBE}4rc7guY>oV@*1={S7&-dFU$z+=MY7-Q_##VYA4i*eq zWK!?1zqkA2c7K>wZm8%%yy_6dE3ruT=#q^nMwBcmEpyLUL~AI?dX%teZR2QLUW(RD zl-)tB)u1*}t_fx^p>)m6$pFf9HpCSKr9)I@!_ndY7Jy@fw?M0Ltx_1u39CqU<&B9u zS`+R>sN-?)Gnp*4*BPk_@)mf4HNdQh&#MmGk?l#PALbil!37{WT51PtbHqB!7OWvD zNrU^rr?5^W#Kl>HKIc2f@>NLPIJ5Ku5kNx^SYqdGI{Mte*-UHk?L`*;TMn`TyAW)) zTZt&jLo86OWzqqra<)&EsHcDZyosKUzS%w^Wv{%1#Ltzxqd3)NyDsOWu?*}d`?bv` z7K-QlWF9?{H;}i>FriUUR0Qg>Jyq82_V|60Q@>|~q<}rPdsd#Nj@Qx;Y?56RudxjW z$~|IUSjf+dmvruCnZ3`#?(`$2rdjBz1GqJU0N4=A{1wRX~sUJ<2iq{hv@l`_|doIP@zlhqY=ru86@3A z-vAZ9$2YtZNt)@8pL$ymKex1hgr-eeBl@)+@@gaB$c$PZApMyLZLbwN)H8m#i9$1s zqO!rw?GN^6kByvsDLR|ouz$K;U+%Z73(fjBw79qh|B+G$B{s!MfNazyJMHg`OT@pL zJWlPN7yKAN4kAF@CbA%A{b6t*@tVL)MSW9I+w9V`Gl3f%*GO!crqR>8Ozp>{KYYBO zUz25X1sI5xO$gE1b4<}IN$-BUzH3NhO-*b7dViOvq}-uG9F|WJSC0Gp%kSOp5Pxaw zCbtggMT8DWpqllPYbXH>naesWOfm*&id|>p)EV{axQ)6f1R}625GUF+!zRjzW@TYc zjnt4Mc(Hg*)yOABMs^-GvikB215!-IgYUE&V!3&s*mwd3n9(-XL@)HTv*Nj!4+ssa zq4-^U^(YY>4~Q;DRgw&QI!kLY4b^nm*>SZk)`;9@8blfjf)VR!LObl_wv$U^RyNvn zpb{ppC(25B#j}Ck6di|ovJQ&F?1jI%z5QciTJa_7ayjpt?L(}tu_lg7Xm?#K7OPuR zOl(?Sc_6p@&EfHS{)hLw^s|6oCD`m~()rTG#&>u8K3?DNw>O74C{1z(o4u(}1^kEY z@p-(RV_8Hn$%O_ohuIvZn|cUpqP#o(75{ZR|Awe(YWiXEHw8-URz*4TI69X2&F=B? z{tqUy;Brcfr;~%l*$GWUT5nEq$r-#rE6><(v5S8Db9_iEo4PW&-oUYqY%FNYhV*5R z-A;w}>4Vz#_Go@=JaHi4M?F@lh{+5{tR-02}Vy8ZHmTcoae8^ruqGesP zux3Pp-d}Fo zRM5h|v1tkBcP(1F+$N|ZvtYK9FAE8Dab#?tzj5xt z+mt!C$pf0bOa4GJf%@&gsRviX49BB0C25|*qc};&8ox=J>|ayLtyIyPbOE%=au_F% zVH?}$=j;2=6xuzTsm4|~_QU1Zrv4tuRyF-5{S%tkhV9`Y9%?8-JEh@RxYDtSe*HZR zyWRew#MpQqE#c+{#v+pj-B!HQLQ2kmQu8HpFl3H+)il+hDeYo6Ny0RYmGPv(9|eJ7 zpZi=2Zq*KWYMyg_gQa+6DZ=|yKY`dI;ZGcl%3?xVQV*UOhi_cCzeNgCN&p+IVi|pn`3!D%4{^P@zW-7hP~Ps&A={Jn z(R9#ZB3TSG5>YuEVY`2dSLyOPEftAZQoa&kJZPn!zcz8TOw0{kr#IVUEacINf+lIwaC2lm~&$`oxvZy`NMv##zH&l#-hL1&B zO1h`Wp{y)upesLup_4vkgB<^ez)Y9qGr_FMLz3TIh(l zj9(Lt$CPm>sanz=*zIvys3#wt{M{L=HXi$CirQ+90=wJ~4fPTSewdG9{a0nG8ro=; z>NDp~UZ>|2U3NC`Q<{UN_W4$J%a8;~evHU3-Sa**t+(_2 z_F5b+Bs;zI&Bl?FEQZ(f8;o1Xd^t~w|p)TD82FB{#$Z9j_vc(S~F;d zO)+@Yg}1sDF=uu^&IIbJ`*hc%++>~`SJ(HHc(C2w;-ckP*y;&RZRM>+ALjs<&Hng3 z*>~J7J?XUdCtXe&+}V^@%kXp&@szyEITCJDpCG z4jU}fF6TIEZI?>TvYueDd9q=pZdE#UJ+_HpfQ&U#^Tr;9Nzf`)2hpHX$Va8N$=xX)VS=(LkrrIPQS3Lr3ifT#Tf@IX00E48F z+w2|^wT{<@a^`_gqDjI@q5y8Tht1*fmU0Fz$iXb~W+ZjR4xDmToqscIPk-HK{o$aF zpaR{%E4?dwCocq>q{(J#GE1G7E(U z*#pM-jNS#mU z#Bbr}9wta3nMd;@zHyL|pG1kxTA4&>`;l1K6E5KvMO$L29gyKz4Tz0AQWEPg8Kknc zdZsnqa6`-8;dv%dCog=H`s+5_P?fE!T_y&|X}dj6Mk2QWGm~xk3HA(6 zt?V_RV>w4-_fE3IbT!C)TZ933?t`khBrU4}E5L@KMz~e0vB+XgLV#Q@(hz*;#?~}= zR1QCEZCPQO_fQz4oe2QUzKb^Bz0ebg1~DlBUWxm1&jTcADOepMoph0v72;-tq7$&i z9xl^OM+Rx|mB5YrdjgK9cA`k|8kXRiqtldXwR?)cyCr^ZaNGF$HVVa@kSMV#0!$fw zu?K(uH9JrPFc~s%t}duBw&)|UI}N+jBJ4K3*`nSn!^@uTJRf?q+aAAPFTd{ha{ziI zPqkaA_dn*hxn$LE;wKuD-u*2U-g8~HVNz8Mqe{9L42Opw$@lgAVkty5q12a4xpm8* zZFa}xIdy)S&%$8k6gEkCYClp8?fdKQc-n0Dp_!#Q1G-Y4(J_Fl@(eV4&^*I43ziU6 z(F^d=a-;9TF5Y$l2sSE=J);lT&{;2N=R!~&eW*JZScX#YdjteKD_o&rP=|R08cs0b z*oL;T4whpSn%#B-jUqTytf+>O;-lQ91lmY;e1MWeG`M@&fs8H(`uR`xBSiS@GUtdpMN8nc|tN*v=kEc)OT!C;&x!z?k56umJJ!xw>)2V zQ%fqumT-y9{^|Di`>yD--7q$fwxjWINd79P`{nI^zsz^%AX{6XJ+g#3GE83n_rvLF zeg5E*RoQnThE{Zk^oBaEV5?0aU0tF;!+olmNgINnLo!%>9i8hy15>^Hq+F=J;~5&Xpo(VZ)-jXi93MYl!gfgSKzI#rA@^_ z8Osy|&!y*2*$i@Rn#=63@kW}+#U*EIo;DN_;)`&`j{i?h(8;)}2oW@oa0_#RiANyMlrI%tCqg1TYBG>6;$5lJ-v zr`&!+1AWn=73rPauh+}l%Wi+%9*$(!o&&;uG86;$bGV}qA~c9Jo1ER2nl8%=tZgeQ15Hz{bzXp4Cqct$b0-pZ#Nu)zE%i9- zY-}F+vOdD=Yur_ZE)6}vx9$R}@m;HS^!u`wut?z>miu_vJx`kKiM$a83DK@}-b(6N zNoI>Rad-TFd!LiBR_*6e!gKR_zhFCQsZA~3c0Wg4zneVkrXILdE$yG<7Jhe{(12;Z zL68I|Jwph9Q+lfb93~@?$*q{hU~dbTD#eAlR8=_hu=G2eCH%sY4~YX#qWSluxV`uaJg?ke47gI;ky_<7Ee$ zSqqe_y*{zB{l2~bp(z8Etgn7?3=M*3GsA_QLAv?0RNBn;ZyQhxn4stWrlT5amv7Y{ z88<5@C$@OAM(+Aj83O>MDJmZm{Usrx8=J#7j^mU^^||Cw`pn$%Pe<7vzfV2*PYqqt z;;7Gyt#XxSx7j^xQvP9e<#(jR!u!fDkaa0y-kofBr*x3mKHqEB7d*^lD1B{bIc1Q? z_2%_HUM21vE)}?0jTf_$<%r4J|ND5K;=>W>sP1kc2e%t7?=boBAu-%vIl1-B)7T++ z=Qp&rbkkFobSu04X}^C^{E(L0M57fbHkE{0YJg4e3CA?DeIHCt$5`VCfN+*+&iUrkZ$+b(+n_%|rf# zc3qZOzF>xNwK1R&P;$L9)@jh8VUthNbF!dA-r%X^jo%=Ra0ecPE(inY0eT0r72O~N zD1v!O+q@#LAYd@g>8+ba5SN?oW#nQ)Zt6h zHYxws^8J(zhb*+hm}n$vWcp8fNC8DRAR&lGz?ARia@99e`+PmWZ~@aZb*H&uS9H6? z`R&jBc1<><*c3ZASiE!|xk@lAV2VB{@7OK%pu-0`kAd3vVnK-E6eK~W6PnK?1EWe+ zXn0e7Zx{NU_zY6I+Shfi!6%)e1aFvdBVdL2jLw+BPX#5Uv;`rcBsVN@xLGYN%w9H2 z^-Z?m4tsZwda?wN&T*w#l_^{u1aE0hLx^l9yIzkHXq;Bz#*+a(jI$@^?)fO82M$u% ziliYPb(w4yXCs8vJe}A9yY2pIyuQXFV$cg0C{SmN%;57-aXSlJj(<(Sgo3l+r(ryZI#n|ODGqfYUU~=TJzyw@->L`Z)*2t_^ z4~mGohKJMa5N!fqmGjuR#Wv>AdlG5KnKu&B3`&pLlL_h>U)piFO^T zY8lx=nQHXIOi~kaJlDaHf@*e#E!RcaI9d>&9S(jIE7md^EuqVuKcG!Is^Uxv^_$`;s6$vzDteKG8__$24FZ zuhn;-eGa-lj1UPSCJ|$*iQ~nc8)iEzpm%M>ItmjG&fDdF{!?I3pbCX*R!tk+BF*^j z^drSa-QEU-MX^20x3TEChegkw;{_9eB@A=ac0NJdX@7Hh{o9Ops8Z*oQ5)I+nwWX3<1mk;M(@o!+Tb8*G$gXM_n8tuN@W{>B>roHA3t39dxd` z=8{p_j(a_5CzDI}ZswqZ4B|zoE{&t9!c5rqFF!wt%Mc8+KKT=9JqtQ62W*6D77}^c5W<0pmU{%dT_ioN}@!YTfzknc_ZL zV%Ss&8&zKb;DZ@T>#4sz{s;+X{XL@rKEs;Q;@U1$KV=ej&nYm=Y*2;MoRHA&O z&F*nKLD)&&GAv@8@}oLjlsF;SVPlGt*_~qN`!*)HW&wOEd~ar`nWKhABZ|%8^drSN zUfxJ?6Pr8qDzBrPfp=<9{O$YeAH=r)_?Ycq&^S~gNHrxp@t*7(th!q_jI_NVLhw`z zG;t-W7yv`-BB}y!D>~E3Rwewg8RBa4B5eX=N9xmkX;C*y)VHzm_i#f2qwQc%6y!DZ zz=<@2we+BX>5>j;19)l1w}vf9dqgvoEjfEWrCeXPcCqwm4VynK(6~kvch*3I*e?ta znG_&c#R{lN3Bplt*fbVBL(@vD-5GTqhIZ{eGAtFOhXr}EFA|eR&an1GKDwwX3hyPO znzDQ*wH}7pY2ahe@_B1DB8Iu-uId-Jk%={}r$F$x&-BV8XbpnnFXP~4M zWU`lt!nhUKPV3E-0t1b3G>8&{0mu=I)&shn3W(z2@$^nhNpS{4|0MK1ci)K(KvC#AGQ?4nO9;BjkcHm(k z2^MX;dJ&?d_wk7hbPj=aOQ%ppHRPyM4z2u3mgIW*4C}k$X-gzx%m@AChhbSn6gu zvyg8ew!7oE6nAoYkszwV8-eVL(8MX_Ah#jb=V$Vk_BY51rh?pOYky;9oq>B)dpPWm z-{X6{y#5U1WbHp#nj?h8v7Wu;>-u=Rz5M!ncYN6G4y#0gHBN9aHa^A{h@W*=c3tIK zeCH)ls86H;zp{tE^=xx&3 z6McsMfYIUHAN7JAH>RK_ARSPioxQ_HjUL=Kr*mq1K2GRw70~}679>VDij6j(VxfQS zA2$=sT6}A`wl#9rID*y|jjT2~6TldJZWO=G=b@&FloyAOWQIG4JasNB1U z>+AoNxhvXZd~myC29A!~lt&ZyH*aAM7i{X~AW5T=PC+q^Y(8c@N9Xsq`|Wys{GMq~ zY^|QMpCgpC6TGmjY96TEX%=+LJJO?Kdv>@_(|{Jod=z5bD1vDT!Jq);&dj3SelB;? zD9phkDJ}+<4H-FD(Mi?Zu(KLCpwUHgccKO6F_e|;RmYn4Ud=hC*;3#8V+I;OjG|z) zD!Rf4gGR})%hoc$b1-RG!e9WrEeUD;4h(w+hLcifn+zrmGPAht)?{3~O@JB$Ao&ld zW)MV=DJPB7w_ry}zJ^Y_UtxMLIuxu*knWK_dvrG~t z;+3;HJb9BQhqj(cK=@!iuLFR1koeEk`C6`Q-4j9BjE(EuY`C^PKBuwX`Bx6x7Qbz- z_h80bdm5P@o^I!VINpkUT1IX({WmI2T-RD`4vAWKeTy}5ndc#Ydxwld$_uyz0gDHk&>JsI$ki>CVSXhT0sj@4q0EgR+;KPv4;XzF<7fL#tt~jF$2C z{`z~f-6ew#d16h3phg=D14?BGg-m7Tm1U>NmWM%W-pNMzFI`PBS{H*IeSTT3AS#Ew zj^3=i!h|2m{EYoA2<#xR(|i>%cW1+2;h=VNYViZ*=fS!gi^Ygsm$Gy~B5mmQ7n0_M zrcESapX1K9B&%Wa6IeYOS|`F~Pdd6bUpwx+*w-v*+9_%^j&1O3b{bfSr3l33N=B|Q z;0?R2x=pk}s4i%k^lfB^T%KDtaljhz8_Q7q=QYK!dR;l4t62yJ^I~T1GHfQQLwxwh z$=ZKuwZ70sD;GE=s2;xE-+%FT5pz~lz6jbqsgp)<*c>vX}f<&j(~T+zp4JNQ(~fYA>+pr81qNh^ZWI3et7zU&2*5l-T8KW zP}xUMy4lOYSdxXF@9zo~j6FqfW6VnDWzl`B<2v~$xJVU|sjS5`TM8K0nisOe$qJp{ zhz)D|5UZBjg>Usva(`<_d#xHPoLn(M^0Q#=mG>`2o}KC0Ct*$||I85MQ10lSWfE3E z-KK8x<5sgr;9CW5*o7x3#tF$k^YH!l{&$8H*qNd8vnlhi+Z>+9``dW^-nE9A6)J&bUsM7q1zlDV>5rB+UF0@rj-k{ z-2D@k`(bj-I6Dlbc}sv_1(r5=*rfeU+)_+RNFaEFYOYlsNe+q!*-z*8difQMS*w3T zgNy&E%49>^tz4em%BRr6^Xu>UH^jF3g3)$xy!*Ui|TGn6>ezrFCS-Q5Le60)GEG0EVd3ENK`bIkb$E$zYCh?lXuvr zb}PG&RghM9vS??!jeAQb%KJog`mDu~1~U?DsWh?kR<}B;k=7fpwSg`se$x++cKK|i zgMt)`qFpUibQ71~C1G$7hML>tJO-hnbt)K4ptFT*L#68t<6U-IG@f>-W^6lR75Wik z)4O;IQw=VtQ?a&b$%?z=n6ZC~N4g}hI08lr*Nc(ve&t$RhIAx|B zp6=H_y!J9v_$7MW=E*u~AI&mTQ`J_BK*pT!4=7DA7pv-@swY zavlA3`QYJ$ackV{pAwb(?eB7sPLPyt&gTYZwv!l_aM@wl#|nLZ`S0y^UZkj0L;Q8h zsl1tCvEBHcW(=^I0Pvpv!{PW?{mvKnkEPM@>wSBD8Q$N1hx-_tXhq-Pg|SdvvMd?X zatpU}^1HVx5nu2@Si=i6etH$0h20S{G<-NuJ*ZQ}me0Hd)2ZkjmiAUn;|)MTEBEzQ zfr60xG%fYS>``{9MxpFi_Fx3stp!mCCWeOH)dG^o2REZG=hFw$(&F8fijd_CVp!!q zsZulPGd`O)FKk0q9)(t+O{X4(t>>DRQ*JxZWEpha8_muPt8^St1{3XK7MrEr=|AKB za=W}{p(p(3XpH^zzMf&W%^~e*Z!^_F&}n^oSG2D6%}lO!+lk~iTJkx9Y&1cw+cJ5d zG(w4`kX-A2hVh188eGkC1dFMlyS-xHJ2__jNg)G+J(Zo*#^6!k<)rKkdjRdya z&(PUR(g%Z*-gV3pSA%hYAXh*t>ub@txzhUfILmB(x()x1)lZVfWfXm~6uP}H3H$!( zQ>?9JsF=sF-!`!qKjYIDdh`0vrs9Mlx)dd;p7{w-K9QmvkD}n@4Qs*zdbK zo5IJIp-X34M$d7nh>OH<>#>JpBktL_!^**r;l6b2*=k^V`eqc0E3P z8#Wus;R9Op3`nEK+Ml!rLTyl9TkhFt(q*kunwqx_(*K1@iSpwny5>$~!z_nX7F`}t+O{1GWWovRI~>k4gOGEwV}KPIZfOYIeOgJZLCeW9*K z_zTP$`feY#*w%b2)5=ef!XfsF+4LFNT-t`3PypaLa7_BC&HiE79B*;a8n5+6&qzGc zmIgF5^EDgr%U zp-0k1K$n4lYG+Ycxe)ZpoJ%T}XN}=Ceb;e2EC;M&M=)vD8qLKn7GS-kpr5=*oor-% z58m;~9zAQ7${x+PRc%UX*0*#GM@tyuVj!q5=TcJ8sk4%TsnyJ0H zV04hevEr*Th-AbmcA3y#h3SFH8B7U-NO|qWm*S z%w-Lh95TS0G{9W^*vM};hwrhApO6+*``t>d6|<%~l(SsnAuU?GUy>sriO}igksbHY z!KIg@Z;p@27iA70&+P*UGZ;3HCKPv>-`!?+jH|`<@@w)%S)RdqO&5Uz)scBPzrS8C zZ>Ps^!>|B6$!AI(D=i((iFxN!d^x|J-(U8}$L(%U(|&a}R4!d3t0xP3ON3?*zYKR( zWE`xKHy!Y49v1qPamNd&;%4igBzgdGtF;%9LPbFZENwABGusdos1mBxpA*{3SU=Q;sEm=i-5g?2|u#szq5;Dfnb;#2c0Q!U|ODa@6Mkzul5w0|%CN~A3d+Es! zysk70t=3#_-9S-u*Q80UcMus$B$mZ3a_d)`mU1<*BuEvRO_X387T?058Cd(#TuO)nx=9VynB*K?S^Z=mLf84_ow*l`u_I> zDWIHQ+DLN;v;aEa7~@u8vwgT$$EAQlZh# zD&qPw#-Dik6@U16dYWTw%3P5(*oy-C!TFKSPuJT^BJ>?j$=)f2q0%FmP>|M7=&9Mr z9S4x$trh(|Ftl0^T93LWrH-gxY_@`rDDst>1yh$vWd9HETi0H+C6JHF1{w z{mLbHj+cNz!<c#`eLUr8gggzW}9 z9IzgzZWCYqabkT=1GZb%sn@+$ReA+a`78U|{3L9TPZ)-z$>eAPhS3t~>n$|IZ@OL1 z=gVuF7VZwk3N&7zRIbN5=dnBskL{25>-pu^{`jy2%hL9*RPV_mEr##-X#8K5bx;V> zR8$p9Y_!q|bj?;}Y_OBnN`-HbI8@s*RI_%$wQCm*%doB(M$c=Dj^Mx^HA_e)u)E{X zpsJw-IoXV7T?iM&Qe1`8D#!~y$JQUsZ8*ZhNO#FrCgKkvI6xgWJiN*hD8g1NVT2tF z=n_KPXM0Vji&)WWLK`>@qYR`5vj!LQYj*siMf8Df*7P+$y|uK)hd*Jp$+hk-CCoU0 zL0y1Kqa^idvpa0|Pf1ICzvT6Ij&I6JeiN+Nnnz5_XtD$ScAslv%Qx%{^WZWEr&2L& zPk+TDWXYdnfWSu;LL|;h@r&&Aip~DC*&btcp8HqYS$1cS?gNN1*Z8%84V;@fK?Jx+z%pcDS5(*RL2LYi?T>UD^7t338zsG zk^)ZesS-l4iUdQ>@D`sdM5La>5enR3UG-c99|?!Jt8@h#JKv=mO95XY1k)!!t#Ewl zVB`#J7TSb3w^@S`tk80ccBMd3l+N5Gw=UogYUqxaW(&v7DIwv5(@Akf4{)h6RCZsD z#zgPUOZOnUva*WViE~LOhs)2Eat-)^=xTE}&Gf&G*OzImmwVi?E?$lCV-G4!wi|zq z_xBl>D4Y{!YA8zX80y>z#^Yhq4JLUDuJ8Zt+*tTn22gv__;H3|o7Ca=yNXWbR>ET8 zu|@$%YRZR+Dg5`yn;Ri$%UZ4Q8r^UrsBLz;(~o#!@AuS$14;+XgB!R_g(Qqm-<32n zlf31AenmwyAxhLdvlLMn;8=;t(j?6WFK-GzR0~6P>2m3f%(ye|x9i*M@7O`_k0+{g z`{-y_wA3!ce2FW}>&wrSGH`qdHlT<0sq4_RtTP*JAnGzLr%g>Fs}vZkk`9{K!P(Rt zE@9P9foW~*Le=dt8<1-AAUa4=I~!EX3Lwz2O7cE^f8d#4#JPeP95tcJ_mKRLfTza3`>sXMqP zmU4)D=glTzwFzc5Lzra=%V8^o(;CFKGb+$VP7mF=!{{c zrQ$Jh9}_>XrNS0Be!Q&-so4c6At*bHSm{r>VN_P@u|^Jduk zJPZreq0qsOBWw{q z|I`mq=)uh5eVrF6gf6|>>Q~V5d3I}(W3|Xn>)600hp)EKq6MePl2-zn=iga#Y^kO; z_QmnAGTJ>-U+w9f5WvAWLKa0;`(;+5YX+9(wb;@1wkYgO zydi=*de%sxS2tq&VSC&io~Pu~i!Kh@tw4!N7C2p)zMK6n;S#Uo^$mX41RW{3>A6^^ zC3;?>*4-~M8~jwR*=XPs7v)D7$21$vwoPRYBGuT1F1P_*{6Npcu)DqeL*D6yC#KZa z%#|#^VWP;qRTQDyX>J%?qfB5fBlu-(!`c?ug@_D%~>x5s)b_uws??)_?#vuQmYU! z-BYPrmUZjt&Rv`}^dbeFIv^XBA@qdK-uBH}{4Z_Hf=NRCYIW7py>pYi)sttT*)ni7 zP~A*ay%4cxx3NR%kR}8r}cRH~%`%#yHXtx_?!EH7PbUB8t+cHcQ2fF(7 z;Qx>vz)2K{-mqHTWvj2eurFxZutL_bC9g3qFd8JPixmL>KLEfwS?Ot}pDZi?;&_?T zpjnJVf+{W`KE9T@Gq)uZR{Ix|9q63$w}M8XB$|~E1_jY4&hmaDKHonFHTr+ebddeS zY`XyjDkm!2>UcjrD*JG17!zmtdrFm!ixvyP0^L{7*N8ayU_px8w3j}N=UaVcqIV@7EBZnP*fbas|WYIZ)OB5s7x z+DlNVal}gTXTOPbBY1ctBUPI(L;OnSsFj*^AchKAK~NPMBDU^rdae*K zIv|@ej53X`14&b0X=<(5l!ss=$Pui7ckx^6rv}uDSl)dJEsf9aWwM4ql`x}hVLBPQ zU~V{ySnpx8Io{uY#y&9{lcU%6WBI@t(J>*5DQWxrEg6%(hcy$Ej{xX}pSL^xh*dR# z=@@eEYiV>~?GSA_%j?>_-i&ihN$Nd|D-#=wCHJF+3&~|R7<=&Z&xz0i=eUP5X<39q zFoU0r;_r^nhtqdnUk8kv%}~BJ%m33q%J1uc4#;%n=_+qe6M^z|YUJl7h|{;v>EY?u z94J1;hqLtp9BfGFG%XC_`0$)U9TiMt1M)eAoZOD*_qXfi?cw=9@jYu%E)z7YSf|v! z5pJpjG{zC#t%gdNYfnfvrU}w4Hk<|b3RWqHP7IjRBq(IY`mwX$yzA)kK`yhFz%gODHNLb2UH-(wFx*=|(VL)xHl!#<`p zvzx5%9{&>;8{Xa##J(@nPEe&e@te6YX6kA%_tisepW|7*%~9xiX1uyL0UW)CVfKS& zS=(d(dw>1?-`#${Kc3Jy2DLL78kdM=c~1r%FTdl<9Uh)T;rK}2Ge2-9E@EfDA-C52 zl9l7SC0gpLY5$>2DhWXh)dq=HE#BC_QJ~o3ke^#yE0IxL7qklOY{0Fb)Fp)UY0(gW ztg1#Mf{`k!;}bL)LDBD`CZ@!m6zfehUMM&|gVpt@FGSg*WpxEf=UJ^(N4fz8olU9} z@xxFuoY2GoKQd^jh63y{>uEfheLW14P0&*iNocf^Ui{Iq5jg{{%I8by4@ueIvIlIbw6tQtwVl@|WXNod)C!T{MT?VMu-A z@B8%~R+>^`x4n9?T>&?{UG)^T*w<3>pe#u{)0s-*o;l@&QRNO~T? z()RSeL&`ddN58)P!*CBK=n%$jHFzx}s%>aoDpHo?&x|gKAyfMwwy`!QNa(~;MDACf$RDG{q<#kdP>}CZx3uO-}-bhqhDza&740n z4F}KRTh5}*Ma+>Zr8BH5iCqFB5iny zoo{Zn0C@$ml)vR*!NArol)Yu|Dd|rtBeJ5mq=>4f)!P{}D4P>zYOJ_J6`VSujAu$> z-K$N&6RFD`-eLh>z&6ocfvMg`h_FT`8M=uzakGC)TIxAUqf+>?!f-DAHq!ywePV7V zHoN04R-*Gu>J#C%1%#X0ST%f$@cv`-9PV(a3$Lv8UE+7xD$ify%_yf{P?W9$nDPkG!lvPR9?mJ87sBN;-AC*P?SUC0oP0g>T^7(v=xIzb(ZGP6-S zLD{PDsX7sjhO~^4V0}$1`fgJdR5>IcSNmR1e<5u)x2Pv2h`lawE~U(_x2Q~SV=~vt znb6i&&Sgc?VKygn&6}9s6leDk_s)~ga``#91z}z&VYTA*1=0pQ7S_Ni7R&K}&7{*s zkmpyJ#UR)o|4KTJw_lT?rr&ylU+SPYyXRs1FbT46j?qAPwj!vR*>xd{pC^{H`#c*o z{(E#B=oy$MOl!U15}QNZnj}KYv{ zSVMHb6)X39{p%s_owujU+y8{|>N~pppnq0dx!E4Khv(}=y7(lTZv7ybNEi3(pL@|# ze?<1^_rGza*q#0l$7}Kw4Sko`raHFgd)5tZa_0Byi`iS2t~ZYdnN0Y8W*i(V6p~Ii zUC&A~!hTBU)|1K|5YfLO=T&68kB$6sU|}j4!*@<5qUY1oxA>Xs7^V*K*)U!$*LgY@ zm-m`}hj1<#{(bXoiCmJ{8R|m?n_zT(aLy9vGtWrpk z6Je6UCWDDU%E65kDW?i684OY#RwkFm|Hz4_SG*CuWozEuG_ws!98mXveQeuQnhdFr z0uDFT8wN?|xJg>-r`vmq(r_6yC^=oO;fKSKQp>tMP6D~a&$A&>`OJddiLhjvW3xH@ z7;opfCNfLvsyEB(Vr$;@b`sj$-u_z+5Q1?ft@AfX1Uw8=w}9yh^Wm`Cz@X^Jj5=-U zp0~+x>XH=m7iCIAsW%;@qS`kx4F^(4ZgQ=f)*C1wtTYc)=TkIG?35$(6qmgz9DuwcxxUXp6d3( z-GltJwc!T?3WETWpZr)akOE(9Qn|KQ==}%nmYlx1Vm~#Oif?mu|FD|&)uU)7#^G)TexMck!clA3h4eV*htD5pBGt*TO637Xp;ek9`a+dmyDc>_JKeZ?}AyysI*y!F71|qXdKZKJ_om-B%)k+E@!aBr@9fBsU@y>P3WR+9 zhaYFrnMs!SqKYeDti9v)eJC@E;`&%V_w{lP zNkGT(v_G8kNL`R#A1`8<740J^?w56RVjDd}gCY&pDtBKH-YT*qgmC;2ETh2I#`w)+ zWl@7x8BDQg3l`9%&NKp*KqGVuIA$~qq$9sm*JMyEsb+_Cs`D=;3V@)2b1MbXyk9hA zCT%!vLyOT&B8!5(9#aR(%sda{1yTu$MdBS4@|NvZVf)uk8wy(o3k)wu<*Wj=NnHxX zmuF^P%*M)DDftyes8Ae>a*(Q{I87fAbu+>3@!POH#)p3#FEctzXN1l?4^cu~$xNI6 za59Q}o`zr-yJ4fG`V|+QZkG}J#N(8~`WpMheBz87SiSw1aZhi~n)dPkNm%XmkB6m9 z3>8p~pp~{f-?p1XXbEH7RV;$p%=Lx~q{^(+g|`f-$4Lt48a8Rs8mZpfFmN}dqL#xi z7jq0_YkKga- z*La+vDmhT2P|AJ#{u*ED{_ympCJwX~5R0m*)%*{5;Me6S|8`uXX_H?x?SZ0T6V2tVw&`BYQC4MrB`#C8X zvDTJm^BYinZOf52sV1K8X~1}`F3ebKxKY@03OdFbpTvjfU*mYj;5VP}yAkx9>L*10 zIGx+epG9BIb|T0!l5BF(X5mm=K>x2XR?O>bbJCz46Ch@luAw)<56y$d;kwhs zhHw~qb3=5IDXfj%*r7VIGeQL&EF`bl|9t#NCkK~QDD(<=DR4C?p(>JCvvOoVIL6KM z6e1P3;D4`Pchsmkl~CyUi@kGfIa6TDG{5RG)`B4xurvJ~=5XuRzf*{mWHixwnBk*A zWm<1Ghi|dO=fISY0YY@5I62M=#{2c{c6|wkVYX&;XSpu($~DBU_df~nJ^!YO0IHbr zc#kD>yxo?6UT^pLSI*)}w3yxTTP%j}fBxrwyWekP{L6a^fsH>Je>0|Eod2`_GGAmo zI5rRQSl9Po^97=i3?S^Ke5c0!uR@B8~3{h+lYj6x#3+A&C zPMkNmV7f-`Hi$9|ZT{YnZdkyW=8X9~eID{M13dXxcnsSmq^P&;^(T4mFd;C6$du@P z=FcQZk-J?yh@5+Hg6um#>#S1<)Le|>A&j=WWS&{n_4+t=*IscEm#nowy^8gCV6ky=#kz<;XPywc+iGYF*U3`}KqJ1{tY8tW5q) zPmD(8mOsw-a8c7$?yg;PDyO1+iD?zihh5-p{cGRAuF>4VCXDlQBR&g?Em%L>GAJ_F&KXwRv z-1^g#F1{ZKLbgM z0k;MH_3~Mk^@bLDGjj5O2+%>?(!YoD&_K8wKf@(8rm`B#ONuN*36-50jkhn04?QxFzD(V;B-fxH@lzidLJGI;B(`Eg5e*^N5k!(#c+A{@@kpsFb70E18yn~u!}J;CHn;mhX3gz2 z)QDix^%RhGCQP9?YxU~Qi*YU@V%g-Z3eZa>H?DKoIH`4vOFs0RNfGXAB9Re9&QZW( zCh_6=P?qbVIUBE-AH^Z~c_IkwH%?^Fdz4buX~GlHXDEA`t<2->LPWS6K|iE1!H zztU_Sz9F<_^f61tI`iyS0Z!&y;{tu2v(zfVYh5UggB6{l5IN_m5?h~HneWXh zmcrLXtLW1S3K%dtR9r>h=uT9!KpDdrGkHLKm&;BIP%UFf5qTVB702hf5@d zU1os7yw(E|jf%~qU53ag4j0=x)OG~So`jq`>RD0Jp0knwG%~6&IHQ1xwpri?jN|AO zjK6C^7?Lhsa|>64m#$766HWuB(mBlaQq}rM7O6z?YY|Omc@NbZEK(1PdS7qJwaPB@ z-oOdfqro}UNtF83m{3dsxtsRac&d_cS%V6_6+MzuFg`jvz#CV}7Cb%=xN|6oePkTR z>*a$e*xO?!ugs+vLU3Id9-_ig>wY`g{b74}j<9kpW&q_10sxqp2qeA}V1y@6bcm7O zqnJQRnC5(nP&$*-e<13ugOm4+-lmf#$8df~Lf}A6y@)*4>8-{*rdl^YnQE4IX zzdFCLUOgg|lzcc(#z27>v)N_Jt&A!fELTS;H;&EL{Smle34?_t5{%?dIwkn`Gha` zr3g^4Us><%zk_`f+dE^P!W1laU?pkM=KHN}^&R^(dNXV9R`bSXr@NZKk|2yJ-!em`4dMmzOQPCN6A~RIZ1c z%U}#f8|cHo0kmx*$U0!URjakVK<(|zfg6$Rh?B5#Hk)Kjdz9|L0UEyF&V6^^?jOps zjuPro1DL_52BneP^zGVvbH80T+e5WlF;5~Es*FPA``(;H$6Do=glcfsCS;NEdfNv2&79g6Vamwe78^i z4EPptpJsw*jLEveI2M=#fwR)z{}_kHg?fOXe26}DN`e5C-Mv4(r5i9Jm3V({`2t2! zc`G{ZOCbXo6DEezmS<$KzT4{wjVB#8VUgqolrhc#N#@KMXzP0UJ1#mE>XWZ*bG z3PDFBvjC(WDKZ)YFX~tx2IQ6Ih-|Q&3Qr)891~|$)Iv#az8~VUlto0a8CU3)M?w<5 z55`zOCLv+>ILg3x?N`kxg=xM}obuwFO1&a*HFO9zW`S%hU2h*l?ENIg@%?e;yyBBN?6JQJIA;x^9ec#?L zpLT&>t+$dKp8-`R+8Ekt7{~s0{i@29bM*IjTzCfLm<$b~ao=gUrMV(Ih_T$c!`sy#WGe1N4LtxtTtSJ_mV&BKR*5lsGcuGk{v$=@T)va0r?4 zXqbs-j`J4c_QF7kZwir^ftU>8xKA`p-c<<5Gh$E=L+6ffL=Ngg&P+8)EEt0gkYC>z zq#L?$N$U`*bYx!zFtTKM+XW!}k~GSpxGX3_G6eyZEFHi&Tw*}Er}DuJ#ONg>#!>Ei z*)`Ett=;vf>zzYY>qF%MGcLu#ZiOUa&U^Z3k7VeZ@cq>YHMH$uWU%7ggko8(Y6otR zji)iHPx`|p+Sz41VSh_%@aR9B$R5=|1m{AYY&_o;3eP%lITQ!=LFg!PN0LF_PtHXI zI6kFHZDCs>B7A`8?W=7^3dWu@MHF=wlO%B#%RyNORD-vdX-b(KF(+n1%vDCwY6@2a z`^&w#wD;Tg_+%^mFfL>95=@H8-F}VW2G}>b-!Gfpp<1m+G)7s5LADbExp@RFz1C8h z)Z~M$8q5d}Fg4r&;cTp?u*kFyAR~*}Xq6et3(EzFi$27&1d0X=a)8avsbgSAS{_R0 zIgEyLQ6D3{|HS~&2+_1jvuINu491@UjBzRakWk{eLXRYO?MV7#lTt4I_30JbkB&oP zD8+zmCjiO&QpqjRSVb=y+=Wkq9w6^akJ35D)%rkjR$XIqLC}~C0GO4A|H{SME@#d{ z=weu&5Xz&;(Ti9W;?_iB_%>gfj0+3015oCGQzjPKWptPKsYi<{Fwk?}Ws)zHYZ5@% zZeX0(M0U49FkJv(9Ss+GPuJLGb?<4@8at4k7N2%g36{z7ZReEsd*+O7qP6UtcVq5Gzg-~kTu+1QN?Vexlq|=3e}VfwHP?8 z>zUMH9$BjyDA9922$M18s3DKM;*8`)KQ_tCL9q{s%}{lIfIND|=)`Xp^_8Y|V5)^d zV8|PaU5%0Nyy-f+Jn8Mr*fuWqbZ)N_+Mqlvn42#MiNu9nh+aqXQ~%_}6bf`+Q%)l2 ziH?E|hK%RQd-_9BXMq8Bx#uE|8R<=k&9-(t?mnRz$L$xs^UWqUo0Fw{Q6?4u2ql9* zY;X7l+|+^f^v(?X*$Zn z%)b__00sLpTRK15-*)#C3nvU(ed8m*oz6NY=(<00gyZQrSL=tOC>(QRj4+}&YEvQi zcrt9_!uV};^ab$pcIeyYa@rlA`nI*TFvDL85*imQz!*bJS0ID3@f z1L8k1o)J&X_YV*ET9~IJ{=ET1cWi=u8*?fFO(TuaIf{%agvc0W`fCGvu$^OqfGqtH zqoO>$cAmz8lO+vn(ZE1*_$b0c(X;%+nKu49c!UxQG{U~jXK4d7Ckw7h$jI6=Kn3z5 zg-x54H$l%Hct+@j9(Hvmo15FaBL>X(49c5QL>>X4v^8+W=E1Je_pkqN_KXk7x%{dy zZ+^@b!Zf9*J8GIUhSXmr+-Qqp(--gzL=2R<%Awbw3m2uc(FSl44wUGVz>=s9H#s4 zQUrJ`+HJDVR1IZ#y>@S|ZFAorU&^Xh)rpwE8qDPc&pSj%jE?hW_h6eb)U?S+pv6Qk z!i#cV4}mNZfhUH_+)9u`>;;Qz3CALD>v?Gtjl`17apX!fz#O@PPh%lqwTa(+EK)(E z4#IvqtTwS1F`5@ABRJBw7ll_7hIA*06)+?)V&nvh=>za-21p)CR454x29`+4P;ohA zg$xpd9NZ9giNU%8Le*fTkO#xuRpsS?7(mJflfnMPE+D_k>-*@l>@q!@q-Dm~Uk|pF zBn^M+u5Y8Q6Zy^va;}4dP6}G~;DSy_zWkNCb0d>RCU(Ra{o3r3WxD~}fRpknSKgC@7g$(?+PCCS!e=w1s**)ujAt|G}q4&hwc6W0FuX& zPae4QrZS|&+x2Vc9nevfjBksA{FLCx>W^6zpa_#@GXn&X3yRufl&z9%Wr#{_7BmV% zkij$RBndnvZE%_&0*c9)REOsj%`C%2iEHLi41kf1#xb8F!=|6!C5Ss#PZtXir6V9B zTeAU0j5-&gXEJ4ZevCM3NEqGBr&a9C!atN?FKK z1S6F6kzQkxt=|9qSd^=3_tbYc0(>=`C^pj(C0QV{uqS`y7m||^E-`|56buh+kNBL) ztu%4%;nE&FwdZn%JgL|;=; zLPgUq2W~v}_s@yV7RA%T`wl|ZQe;L)SL9(`UWf z)|>sPmeEo0`%N_#;rVS2PATm`gMjy9y`k)MIJ z8*^nb*y2YT7%F9(er&TU5KM{=rafWP{^&oFn`sLuoL4_`DG^Nj?{SSpq}Ws*tcUgM3$zzfrEC0>EX(Gcd;0 z?itJ^)swjj0J5b-KaKYzNVfP^6o^N%lyT^_>M$sR5Ts!;sHso6I#lF)_iiibu=;6} zY0wv6&8nT=&#s9C_(sWFl;>iZlNdtBQPx?hcU$lL$b>fOO{B&HrOaGTCL=)v%+T zPk|{QNbq7w^~{0E;Fy>jc1^UK)-PIEBZL(R8l1Zyb(z5L6fD;dwvcacZ&Q;5lT9BV zCIY9>c}Fy<8n5bYwK;ZoH%4YlH|IO}Tz6CK4o_}IXMjhDAc(FL6rD8OYQ?>|cFk=^ z@v^mwy~CKo0y2ahr608EyL)r_TJH{aw}?f;XcFbHsF*o#1yPZ}QDkT%BcGXY(&TNA z!DrP7?kxnz5M(772BUX2!osN9KoKgaK6YI=g3EX}UaVP)X`)ebg9koU_?g=oPWG4LEqQR9rLXsQ_Re z#SE)?K?%79^!Im+-a#hivPCOw4j6em+;aP3D`?V}4_(-6W+|8v=Do})BN|^#!-29s z5Z&-*3;B*&Zi9-ARI6;5+)f&vV!gHHbALY%lp0>l{y83tFzMVo1lzFkx~S^a?$s{M zb{hsc(Tr|$3|}kCdcEC08j$L>U?HoSWPIP5mX7gu`Lq{rOQ|ab*Qu#3HWF^--%V>r zb2$%Pw?Dp+87YoN;hYJ|q5Q>6px`2xb_mW3YHv?q5(`ngMfE5fm*WE11{#M21)@M| zRK)Z)dUZEa8OOF05VTLhP)QB4jKF>wu=8!?9IDA8f{a5onisE%I|Z;aPhm`GCcxWw z=6e!VDmgQ>yi|=-`atZA*{%a~ic8)sgQjOK$vdW0z4dDQ+}%&J=LRS6vrrNTtk7#I?V8wrC))riIF@tIf{M+@;qLV1Y&8R3<%sDCe{N@bR#C3V)SoEW z=b10UzcUz!4;TV7H^3P2;;g54KPYv95Rx$_$8v!`8!yK#0OAf!IgZ~qoWIxTG!oJxNKaf4c?@{yq*aB z8~kcx0#Lb08YPWVD0R;T15^G0$cz>=m;^()c z>svn&CGIxyxFL?j*qk6_=b#^_fwzn{j%c^qJQPrNS9XCwxH=gLLC2?vxjfi5t6Cqr z>n}1z@f(AdflJn6(cDgLbKC77$zH?cSXHQrEFc_lCPv4(xn0(~V^yycJt3Hd)NTHE z_Wio9xt>1PoBe9NM;vn(-uwph%Hg`dRXP2c9tuH_1z;f>8ou$USM-mi*yikfaVcFI zv%%W>$xTrFCB|HwmtitUU1}Tt8|kN-o-QL_* zULAK#_Fnbou~-o(i9-j9@$-k*7>!*J$>7Zvo83*Sa~4i=hPgo?D`{KWQ{P@t%5LC3GNIXg>&iPG8W%`^rSt1akeYd;C zLshLL>=$Vo$a7~!&4bZaS+v_+^Nq@90VPmuGNt+qPm3%7lhaT^gwG602Q*|E!wd@M zDPFvxFrs``2WS>F1{z4tEQ}$#8%HT3F#^<_KXWu3FXnNnOt!cX3MtLE1QwFW=TL;} zqU>xqV+Cim0fbTG@eqmH=>l}ByEAP0+k`^(P9kH|5MhRdiUJlzHI#*o-ZMx zLbZNP)o{$Ob#~8ny?vqvoG5YLLeT>hC<(6ID~Ep_mo)m9Z9|9qm$C>8UXe0#1dO8e zIMmoKLxpo{<`ojg>%h%F&@3Px<>sNRx3sV>xsqo8EJBgaJdcXahk@3_CXxDuBA4k$ zP>~?rvRh7@);ahPaaIPiDwxFvY0x&kf9ZJKLp$k)LMC{yXF%yLWkk@MFQRRywWD}} zdpNfc6Lh(is#pH{X)TbwMt8k@t+u<(_5m|o!tV>B`JZ7NZl@2sNo;oyLZpxyDQ4&U z(@JIE#3@#G$7k@Vh5w>%{*xS|Z&GJ+m02Eo&4wVEZ~L%C*mtQ}T>e@7?nD1qVMsZ# zr7lr&T*utC!K@e5pIoLKU0W&6|5X&%Qa09aZXNuS*AZ>YK5`>Xdp^Tv_WM(=cnFjy zYl}^2FF!-E2^n1XH($W6Wp;_~uW#ef*~@KBnLkGjhtxk z`fpuHV~tR(tId<`Cmgte@F$l&`KW?m@E>+b={;)vM{lBEMX-KR)-F47Sbh`y7G{Tf z{YabE&ACl+$ZFv<2Q;d{*+>7<-5Xm(S5=LX*cvFW9)vR(GCO?<8z`UHwaxu{+U^c@ zz2O$f4APf&M-g+48R)LPVHF__x%(q3`8l9c&jaJxM88uxi9lynV5Ny-Tww7dW8lV1}3<8yGRF)~7NtQNs|J6aGdf6pE$3(D9Y z9giEi;D}2WXB*wwm^9Y3;T|Ffz84(s{r%G*;-Bvh?D>TPXiDigi)I{QPJP0}^BR?k zPApBvQR2$Ab0zJJuX>eO)r(RX)Zx`D!+=9SV6n0PHs`lt5@h<$og%qiSU5#C^?Ct-cD(b`i^3~o>_GSGzy(R>QyX^46epi06XPtBv=v$Z5$MNyiuF#pdud4Ba^Nx>S=U@>260Sv>dVLZtHjL*I3F(T_T2 z1~m$ODmRS649pjzp%dR4CFul{5sfpY3+TfDd3xrB93smmasrsb*yOAbq*n(T1)E?R zydhEJFewh{M<|mMQ03lZNI}->crelwFX*;YLH*?ZQlw?C(oeHDVP};2kEvV`%){vb zCY_H0Yg84}2tZqJd#O3ZaB`VM!>@7(Eo2GvjzMG}{%UfGa}78$5)X20mZ|;?&!Cwq z)_PrUCs)#wGQ~u#)s!VXM0O{Xt7=P`r=49BgKj)o8d)Ai68kynoVQQ*MTFIwDEbGZ zc<$`cfn@69vPFP$TmB)`7y!&4j4Dv1ce~)!qboMw$nJS2|4w%hE)~t74B5#gZg0B^ z^5Z7@M{bjVWNMFs{4`R%T$k1UemnKeM^%!>FtA4L3OiE{lDK9gW64RIxY~E^^?v!~ z%=3&6%)^X48e$*d|F)-K*iXBiaGh3UU38P<_8c86z7j{(2$Z~Zej zj?LwB?1$~~v2=!c0NVDH1$78oG)J>ioeapZnPhT>kn38|aBJyk*(_b2RpQg~Ct_OXCGql@ztN#^w5_oxb^+!_uprF| z5?jz|Wwl2_lQim``)E$7OzL^22pZx0jFV{aff7AjArCTmiVYK&u+MqbDpp6w-1v0b z7FfKGzYTdWs%g`@3=}UQh%U1G&!a!{>g@cuacEs3Z*t3ccF_jIGd%2R-P%GPDPCa8 z#DPre9-8F22VvYdZaKo5jhP21CiM#47U=IR@l|WrxY6Lch{{(7T6OnJqSpYTJ9%IAU#qW+rxG2oa3Haw#2Z?1ED#4v7a~fHU)^ zl(=X_9x1%eVE`u%$=Ef+{hR@fzZqxnrGF-aceEykYY+{VK^bcE{VG0>1s>^Y z7x8@jehP(px5dS&+45vC`+CeWEATg?IrAsOJWRe0{%%O}&E;7fg?>XgQ=?w#!2ieN zSp0s-lsvt;V?xYuBLCPi{$I_BahbS`RVF&0-_@ z-E^4d>8{u$&*-lCg8b!Lyj8+}ZOg>f_SJbtf0(6!%I{}kE^&oKgE$hy7l;4#HL1um zdgZ{4#Zf0LvfAc%DV=3i@737(Ir{Hz9H;7h=p!LgJI0IM2L(_3BWOuyObL_cn8O7 zwI{2&``2)Hc}w=4MwWuhNo}Ywq?l!B8(tE&bvHtdr8?gyJ^2x`4?`9#=4lj36_JF4Fm*9gDd5kP+4EeMrMw`>iDUrI`kZ5qYPX)Dsf4Pa` zq{iO&cY^hiyW}C)gUeuPPC0fkHCa}zA04mml=v;q{XQ&X`13fn1I19~ ztW+U2&&0$~keVbSgWo=;(-h^4Q(H$;AQwb**FK1G|ZjaMKFaGNH^gA?57^|!UG2^Y2L-7@pOnc2G zuL=P}26N>-ik^w3BA_N&W0u*6zc2Aqwl=0jw&b@mt^=2a$ddfgVh|cyH+w_z9G7m)vjJY z+aj^2kU;p~^)*f)^WywbPXGv+CQl^kV3q(v#?!B4HP?LLa$AU)xi%2hv|!()fWl=I z44UH&nd^~SCE3N7yl1P8ZEl(Webmsk*YihhcZ=;IutpFecRL#?0Eu2S zAXZUSGmrU<#uW-Up-IrTF(x3R)|pA}!{i)~tvs;fz zn9-olFBsv2r0)#?MevGwRG2sCp5xsy^}+Pk!N|K7u~?(Y-BLWOdrqb0IH8=fFh38- zkU4o#2STVW&9rm7xw91`UR_z=FaNk{4?hDS+@+HUGlphQYvPtXqbY-qmArY3gPm=5 zZpxKi6CdpoZ_982k0R&LVbFMIAst)bmD|Zy>BLo05E)7UdIJHzyP`{eBG2ft?eAaU z10?|YQ$UTvB zP?*%%52VJve~m)}Oc%w!KR3ZnaXh*}6vzJT;@F+<6@=MGY*A96Hkz(GvTa$e?7mfi z(XN$KKV{d}zHj%(=Q)omdiYF%5|9kyr+JGAwQH_tN-x|$*7XYXDgbebM$FVp^X*Hf z{>|lcyFad0+t_gCO4>|$JI`4pLjw6W5v}*q`WZ57Mi_-h>WB!nl}OHl4ID@WAn?46 zjP#Nm6WOd_^7zMPJ4twDb6MdAr@kr1aBn?72MnTaMFOgXOYMZ(*8#IYvEWNplV9EC7oo;U>5frn7e;krPB?vjS?}xhLx1}q zn|392#T+)u;-A8;VxQZ3^D=Z73hGA82!nWdmA5zt%y|k?6!6nErsi;fz4b)zG}*&@#r!yokyw*Au!i;GFZxwdb~6AKTk6 zN*YAZL}76dj06cLNur6+^W;}|KNSWREDa#t_Lv3{OIyfqo`?+Io+Rs%xmZHZKP<4c zNO(xFzCE5|i8(d&-Sz8@(3t&0rr8EuYJWM?3UR!g-bXVM&;f-EU6~W`fIDUDRL_~X zal4$l?!G%d6-5=t$RYU%IZUQoIYw9l)%QX~2bqR75Y@x_N{(a_C#@LqiovQH*%A&2Y#_n?5SjlM*G72^rl?oz{g3W` zw*O*QRLP5W$aLrQCPls8J==!2y?u}*fx!mBltI?qm@j8zh= zIT5wRBpA#RPME_VN5)t>nlv;~qX(hY5kpgyM&K$*D@Dm9K`-VbMAt1;xN4N$gaj?w z-zQB%hGc?VQ7Ekl$q014`(YoI_V&U3Nn{>*|Y@mKINFz|6ov3`xBC#IYAZ&Xy< z6%m$CV|y{aEbzAT;l5XlcfEHLIgh-V?nCpbViX{{=pj=_flQN`bI0r5E83E>d7n$2 zP+Q~_)r{88ChyMRi6q@b&g+ejtrGn!rf`V~cA|6c`1+x&*3{gP$*RKkVcH6PYmeo#uGgF0K}LWhW@crmCrb!mc*z!56HNs}wN3(}7MF6iHA7?}fhT59 zZ%lU@GuK8Dt9e-fuV{~v;U4&y|a~X-+T=1jUmBO zLJ0;SpL90ero?*QKG>z1Y?Z=92QgNQ9NtF`3C7@GyHikjdEe3y%%Dzo;oe39{a0pf zj+|FaF*IW=Hc)q4PU+GPJ{$R`qcbtrZr0n94!7yev%{taAlFN4^ zIkiH?h;I=3?TOqmts2+!XIZWG56`EMf5yr8Pf2e4iDb=jxcg^#tGS+QyF4H4(RY2< z@&IfABak9kkbv9WqPbKn+iX7cbZ0d5VdE^GnyUpL@f;itxGF2&VY6!l0Jpy6j$d;c zv*B$U*8>APqb|}yIQ7ePbU!qV%4&qjoAZPGpvZvWOdFKVpd!tIp#*8>XI4JXwAZsp z9wHzBetluzo=Mw-csMGQtyO?bqu=e>c_ai+QBAeH$D`kO-pnSs}Kvw8zO z)CGxEnO|lgMBg&6O^*eoVFtu{Cg|B#tlia+);H?4o&4Ao5`q`)wO`#zVm&|f?bjsf zMkC0khBLlGFX@*`KO}Rc+TF8#`1|{Z3A`;4Hx$D-#GS^tNJP%tJlm_+U4FXNt?*!v zpQQuJl=kq&fQ6CU>e&|ZWqbY)t%(Jzr+BcvhulDMclbmq^ z@N<)26sy(Y|KQl$ZojH+mtV?el@(Z!lgj$mPRy zr&m`_7w+Pt8l#f0JTp3ksOPC}fcE>8n<)qO@>{PCmN1E2`CpWTkPPj0Xii2ts=CDm zLKz8aH|G8fB-OE-mc2l~AR9vT zlNV%6tq6Sdwg@>B7`;6__-n|!K}gl{j3+A5*4Rp0)rP@yH-~BW9IBu>ipAKT;WuF*&}SU#fHN zIs6+e1w$#ivQZ@|Lq8KoKzTobS9&a{B*P6QfTfhso7iV)0|Gz2lsXg0l1W&O#e6|f zIZo8;!{6gD+^GQ{5DPV@08#R5#gjXV(Gkd=?Jtuq9Jrejs$gW)+qj(RIo9(#c{2f| z0SHDI|ILpRfC!u}S4Ufe`&fViMCXnPy%#h(Iv4U=$04~6-rxyAP5~i$X8=a)D`Y<3 z_Jp`itEB|=-)f3EZGR=vjWMFV%t# z8h~nj{C`yIC);U}c~;*<21!o$0I@Fw1aUjt?_;+LdrtZ}h9)l1i3f>MIw}(N-J9!f z|Gc7H*Tmx)#jIV%&4rW<0;8|jPg{wT*M}s~8XkPHz%QHH?!Iep=MQ@r6s5@6wwUD5 zsHhF)N-dHi)ifdFXeZQ00gBc?7MHgmAlfo98B-Odo zn#dw-F-qhxj?cNXDb9tXnSv9yWPwd?qB#C5&*%TJ<=!0;pt19aR0GyPIh6P*KjV72BWC%C@X~N2kZq}@Bg;lK<~3`rk54E z(?Twi3cWz#M17RUwL3hQWfdG4BP1u{oFk!w&8z9X@7MGFcG(`D%GF8+X((2x5Cwze z1>1GLxt?t)zFKc3_y8s+qmo2jKuwBWDD6-Md_?pzcowI9q+g|Y9Tz;FLD`ZCd!tBG z!)S1+eR~Dj73E`=?yX$2%<@n;nbj{hI^qsvKBI&$nBA2kfNS_=9-+p<6QV?)qX5Br z%OtQ1=2U|U(PJVS=^8Gqqd?$Pp$8B93JatMv$6~8dUwCP_3f2iXb4~ k+*$f8F` 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

0~BjTbon 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 literal 0 HcmV?d00001 diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg b/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg deleted file mode 100644 index b39a55e4e83f3290d7befdfeb1055cf9b73f4bfc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64011 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 diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/images/nocert-bg-split.png b/interface/resources/qml/hifi/commerce/inspectionCertificate/images/nocert-bg-split.png new file mode 100644 index 0000000000000000000000000000000000000000..b2f5a492653b84bf6b739ae270180b1bb7b0d48d GIT binary patch literal 17201 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 F{|C0gl@I^` literal 0 HcmV?d00001 diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml index ee1246c0c4..7dd72b904e 100644 --- a/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml +++ b/interface/resources/qml/hifi/commerce/wallet/sendMoney/SendMoney.qml @@ -1115,7 +1115,7 @@ Item { AnimatedImage { id: sendingMoneyImage; - source: "./images/loader.gif" + source: "../../common/images/loader.gif" width: 96; height: width; anchors.verticalCenter: parent.verticalCenter; diff --git a/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif b/interface/resources/qml/hifi/commerce/wallet/sendMoney/images/loader.gif deleted file mode 100644 index 0536bd1884f1cb4a814b729803b1996c8347d182..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59412 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�)#8TsS*rVAU%J0tpt=B2=IG*djp3b$!Y|O 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 L8@}NiK8F7Q4CAa& From 679d53e2cf3cb4393c2507df731dcadfa1c3627d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Feb 2018 15:13:22 -0800 Subject: [PATCH 03/80] This works, but is it correct? --- .gitignore | 5 ++++- .../inspectionCertificate/InspectionCertificate.qml | 2 ++ .../src/ui/overlays/ContextOverlayInterface.cpp | 12 ++++++++---- interface/src/ui/overlays/ContextOverlayInterface.h | 2 +- scripts/system/marketplaces/marketplaces.js | 3 +++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index f45572c388..1ffb93fe80 100644 --- a/.gitignore +++ b/.gitignore @@ -85,4 +85,7 @@ npm-debug.log android/app/src/main/assets # Resource binary file -interface/compiledResources \ No newline at end of file +interface/compiledResources + +# GPUCache +interface/resources/GPUCache/* \ No newline at end of file diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 2c7319be09..bef03bd4c1 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -256,6 +256,7 @@ Rectangle { // "Close" button HiFiGlyphs { + z: 999; id: closeGlyphButton; text: hifi.glyphs.close; color: hifi.colors.white; @@ -562,6 +563,7 @@ Rectangle { case 'inspectionCertificate_setCertificateId': resetCert(false); root.certificateId = message.certificateId; + sendToScript({method: 'inspectionCertificate_requestOwnershipVerification', certificateId: root.certificateId}); break; case 'inspectionCertificate_resetCert': resetCert(true); diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index ed7b811fb0..77284408cd 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -221,13 +221,13 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) { qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID; + emit contextOverlayClicked(_currentEntityWithContextOverlay); Setting::Handle _settingSwitch{ "commerce", true }; if (_settingSwitch.get()) { openInspectionCertificate(); } else { openMarketplace(); } - emit contextOverlayClicked(_currentEntityWithContextOverlay); _contextOverlayJustClicked = true; } } @@ -350,6 +350,12 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; } + } else { + // We don't currently verify ownership of entities that aren't Avatar Entities, + // so they always pass Ownership Verification. It's necessary to emit this signal + // so that the Inspection Certificate can continue its information-grabbing process. + auto ledger = DependencyManager::get(); + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } } @@ -357,12 +363,10 @@ static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspection void ContextOverlayInterface::openInspectionCertificate() { // lets open the tablet to the inspection certificate QML if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) { + setLastInspectedEntity(_currentEntityWithContextOverlay); auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH); _hmdScriptingInterface->openTablet(); - - setLastInspectedEntity(_currentEntityWithContextOverlay); - requestOwnershipVerification(_lastInspectedEntity); } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 6aad2a773b..fcdf2d5820 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -57,7 +57,7 @@ public: bool getEnabled() { return _enabled; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } void setIsInMarketplaceInspectionMode(bool mode) { _isInMarketplaceInspectionMode = mode; } - void requestOwnershipVerification(const QUuid& entityID); + Q_INVOKABLE void requestOwnershipVerification(const QUuid& entityID); EntityPropertyFlags getEntityPropertyFlags() { return _entityPropertyFlags; } signals: diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index edcd488a01..cec139faae 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -583,6 +583,9 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'inspectionCertificate_closeClicked': tablet.gotoHomeScreen(); break; + case 'inspectionCertificate_requestOwnershipVerification': + ContextOverlay.requestOwnershipVerification(message.certificateId); + break; case 'inspectionCertificate_showInMarketplaceClicked': tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); break; From 704d4255a56ac04027cf47ccc78669f21aee2c6d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 7 Feb 2018 15:44:21 -0800 Subject: [PATCH 04/80] Comment change --- interface/src/ui/overlays/ContextOverlayInterface.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 77284408cd..4dacab8936 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -221,13 +221,13 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) { qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID; - emit contextOverlayClicked(_currentEntityWithContextOverlay); Setting::Handle _settingSwitch{ "commerce", true }; if (_settingSwitch.get()) { openInspectionCertificate(); } else { openMarketplace(); } + emit contextOverlayClicked(_currentEntityWithContextOverlay); _contextOverlayJustClicked = true; } } @@ -352,8 +352,8 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID } } else { // We don't currently verify ownership of entities that aren't Avatar Entities, - // so they always pass Ownership Verification. It's necessary to emit this signal - // so that the Inspection Certificate can continue its information-grabbing process. + // so they always pass Ownership Verification. It's necessary to emit this signal + // so that the Inspection Certificate can continue its information-grabbing process. auto ledger = DependencyManager::get(); emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } From ecc0a2f43bb98974fea6623e1fa1a31d73afa545 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 8 Feb 2018 10:35:34 -0800 Subject: [PATCH 05/80] Bugfix. --- .../InspectionCertificate.qml | 149 ++++++++++-------- .../ui/overlays/ContextOverlayInterface.cpp | 142 ++++++++--------- scripts/system/marketplaces/marketplaces.js | 3 +- 3 files changed, 153 insertions(+), 141 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index bef03bd4c1..f493747c5e 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -24,6 +24,7 @@ Rectangle { id: root; property string marketplaceUrl: ""; + property string entityId: ""; property string certificateId: ""; property string itemName: "--"; property string itemOwner: "--"; @@ -110,77 +111,81 @@ Rectangle { } onUpdateCertificateStatus: { - root.certificateStatus = certStatus; - if (root.certificateStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS - root.useGoldCert = true; - root.certTitleTextColor = hifi.colors.darkGray; - root.certTextColor = hifi.colors.white; - root.infoTextColor = hifi.colors.blueAccent; - titleBarText.text = "Certificate"; - popText.text = "PROOF OF PROVENANCE"; - showInMarketplaceButton.visible = true; - root.certInfoReplaceMode = 5; - // "Item Name" text will be set in "onCertificateInfoResult()" - // "Edition" text will be set in "onCertificateInfoResult()" - // "Owner" text will be set in "onCertificateInfoResult()" - // "Purchase Date" text will be set in "onCertificateInfoResult()" - // "Purchase Price" text will be set in "onCertificateInfoResult()" - errorText.text = ""; - } else if (root.certificateStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT - root.useGoldCert = false; - root.certTitleTextColor = hifi.colors.redHighlight; - root.certTextColor = hifi.colors.redHighlight; - root.infoTextColor = hifi.colors.redHighlight; - titleBarText.text = "Request Timed Out"; - popText.text = ""; - showInMarketplaceButton.visible = false; - root.certInfoReplaceMode = 0; - root.itemName = ""; - root.itemEdition = ""; - root.itemOwner = ""; - root.dateOfPurchase = ""; - root.itemCost = ""; - errorText.text = "Your request to inspect this item timed out. Please try again later."; - } else if (root.certificateStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED - root.useGoldCert = false; - root.certTitleTextColor = hifi.colors.redHighlight; - root.certTextColor = hifi.colors.redHighlight; - root.infoTextColor = hifi.colors.redHighlight; - titleBarText.text = "Certificate\nNo Longer Valid"; - popText.text = ""; - showInMarketplaceButton.visible = true; - root.certInfoReplaceMode = 5; - // "Item Name" text will be set in "onCertificateInfoResult()" - // "Edition" text will be set in "onCertificateInfoResult()" - // "Owner" text will be set in "onCertificateInfoResult()" - // "Purchase Date" text will be set in "onCertificateInfoResult()" - // "Purchase Price" text will be set in "onCertificateInfoResult()" - errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item."; - } else if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED - root.useGoldCert = false; - root.certTitleTextColor = hifi.colors.redHighlight; - root.certTextColor = hifi.colors.redHighlight; - root.infoTextColor = hifi.colors.redHighlight; - titleBarText.text = "Invalid Certificate"; - popText.text = ""; - showInMarketplaceButton.visible = true; - root.certInfoReplaceMode = 4; - // "Item Name" text will be set in "onCertificateInfoResult()" - root.itemEdition = "Uncertified Copy" - // "Owner" text will be set in "onCertificateInfoResult()" - // "Purchase Date" text will be set in "onCertificateInfoResult()" - // "Purchase Price" text will be set in "onCertificateInfoResult()" - // "Error Text" text will be set in "onCertificateInfoResult()" - } else { - console.log("Unknown certificate status received from ledger signal!"); - } - - root.certificateStatusPending = false; - // We've gotten cert status - we are GO on getting the cert info - Commerce.certificateInfo(root.certificateId); + updateCertificateStatus(certStatus); } } + function updateCertificateStatus(status) { + root.certificateStatus = status; + if (root.certificateStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS + root.useGoldCert = true; + root.certTitleTextColor = hifi.colors.darkGray; + root.certTextColor = hifi.colors.white; + root.infoTextColor = hifi.colors.blueAccent; + titleBarText.text = "Certificate"; + popText.text = "PROOF OF PROVENANCE"; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 5; + // "Item Name" text will be set in "onCertificateInfoResult()" + // "Edition" text will be set in "onCertificateInfoResult()" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + errorText.text = ""; + } else if (root.certificateStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Request Timed Out"; + popText.text = ""; + showInMarketplaceButton.visible = false; + root.certInfoReplaceMode = 0; + root.itemName = ""; + root.itemEdition = ""; + root.itemOwner = ""; + root.dateOfPurchase = ""; + root.itemCost = ""; + errorText.text = "Your request to inspect this item timed out. Please try again later."; + } else if (root.certificateStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Certificate\nNo Longer Valid"; + popText.text = ""; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 5; + // "Item Name" text will be set in "onCertificateInfoResult()" + // "Edition" text will be set in "onCertificateInfoResult()" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item."; + } else if (root.certificateStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + root.useGoldCert = false; + root.certTitleTextColor = hifi.colors.redHighlight; + root.certTextColor = hifi.colors.redHighlight; + root.infoTextColor = hifi.colors.redHighlight; + titleBarText.text = "Invalid Certificate"; + popText.text = ""; + showInMarketplaceButton.visible = true; + root.certInfoReplaceMode = 4; + // "Item Name" text will be set in "onCertificateInfoResult()" + root.itemEdition = "Uncertified Copy" + // "Owner" text will be set in "onCertificateInfoResult()" + // "Purchase Date" text will be set in "onCertificateInfoResult()" + // "Purchase Price" text will be set in "onCertificateInfoResult()" + // "Error Text" text will be set in "onCertificateInfoResult()" + } else { + console.log("Unknown certificate status received from ledger signal!"); + } + + root.certificateStatusPending = false; + // We've gotten cert status - we are GO on getting the cert info + Commerce.certificateInfo(root.certificateId); + } + // 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. @@ -563,7 +568,12 @@ Rectangle { case 'inspectionCertificate_setCertificateId': resetCert(false); root.certificateId = message.certificateId; - sendToScript({method: 'inspectionCertificate_requestOwnershipVerification', certificateId: root.certificateId}); + if (message.entityId === "") { + updateCertificateStatus(1); // CERTIFICATE_STATUS_VERIFICATION_SUCCESS + } else { + root.entityId = message.entityId; + sendToScript({method: 'inspectionCertificate_requestOwnershipVerification', entity: root.entityId}); + } break; case 'inspectionCertificate_resetCert': resetCert(true); @@ -576,6 +586,7 @@ Rectangle { function resetCert(alsoResetCertID) { if (alsoResetCertID) { + root.entityId = ""; root.certificateId = ""; } root.certInfoReplaceMode = 5; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 4dacab8936..dd05e5c6a8 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -274,88 +274,88 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID auto nodeList = DependencyManager::get(); - if (entityProperties.getClientOnly()) { - if (entityProperties.verifyStaticCertificateProperties()) { - SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); + if (entityProperties.verifyStaticCertificateProperties()) { + if (entityProperties.getClientOnly()) { + SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); - if (entityServer) { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); - requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); - QJsonObject request; - request["certificate_id"] = entityProperties.getCertificateID(); - networkRequest.setUrl(requestURL); + if (entityServer) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); + requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); + QJsonObject request; + request["certificate_id"] = entityProperties.getCertificateID(); + networkRequest.setUrl(requestURL); - QNetworkReply* networkReply = NULL; - networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + QNetworkReply* networkReply = NULL; + networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - connect(networkReply, &QNetworkReply::finished, [=]() { - QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); - jsonObject = jsonObject["data"].toObject(); + connect(networkReply, &QNetworkReply::finished, [=]() { + QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); - if (networkReply->error() == QNetworkReply::NoError) { - if (!jsonObject["invalid_reason"].toString().isEmpty()) { - qCDebug(entities) << "invalid_reason not empty"; - } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { - qCDebug(entities) << "'transfer_status' is 'failed'"; - } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { - qCDebug(entities) << "'transfer_status' is 'pending'"; - } else { - QString ownerKey = jsonObject["transfer_recipient_key"].toString(); - - QByteArray certID = entityProperties.getCertificateID().toUtf8(); - QByteArray text = DependencyManager::get()->getTree()->computeNonce(certID, ownerKey); - QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); - - int certIDByteArraySize = certID.length(); - int textByteArraySize = text.length(); - int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); - - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(textByteArraySize); - challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); - challengeOwnershipPacket->write(certID); - challengeOwnershipPacket->write(text); - challengeOwnershipPacket->write(nodeToChallengeByteArray); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); - - // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); - return; + if (networkReply->error() == QNetworkReply::NoError) { + if (!jsonObject["invalid_reason"].toString().isEmpty()) { + qCDebug(entities) << "invalid_reason not empty"; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { + qCDebug(entities) << "'transfer_status' is 'failed'"; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { + qCDebug(entities) << "'transfer_status' is 'pending'"; } else { - startChallengeOwnershipTimer(); - } - } - } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << - "More info:" << networkReply->readAll(); - } + QString ownerKey = jsonObject["transfer_recipient_key"].toString(); - networkReply->deleteLater(); - }); - } else { - qCWarning(context_overlay) << "Couldn't get Entity Server!"; - } + QByteArray certID = entityProperties.getCertificateID().toUtf8(); + QByteArray text = DependencyManager::get()->getTree()->computeNonce(certID, ownerKey); + QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); + + int certIDByteArraySize = certID.length(); + int textByteArraySize = text.length(); + int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + textByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(textByteArraySize); + challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(text); + challengeOwnershipPacket->write(nodeToChallengeByteArray); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); + + // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); + return; + } else { + startChallengeOwnershipTimer(); + } + } + } else { + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << + "More info:" << networkReply->readAll(); + } + + networkReply->deleteLater(); + }); + } else { + qCWarning(context_overlay) << "Couldn't get Entity Server!"; + } } else { + // We don't currently verify ownership of entities that aren't Avatar Entities, + // so they always pass Ownership Verification. It's necessary to emit this signal + // so that the Inspection Certificate can continue its information-grabbing process. auto ledger = DependencyManager::get(); - _challengeOwnershipTimeoutTimer.stop(); - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); - emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); - qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } } else { - // We don't currently verify ownership of entities that aren't Avatar Entities, - // so they always pass Ownership Verification. It's necessary to emit this signal - // so that the Inspection Certificate can continue its information-grabbing process. auto ledger = DependencyManager::get(); - emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + _challengeOwnershipTimeoutTimer.stop(); + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); + emit DependencyManager::get()->ownershipVerificationFailed(_lastInspectedEntity); + qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; } } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index cec139faae..fd1275a251 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -163,6 +163,7 @@ var selectionDisplay = null; // for gridTool.js to ignore var certificateId = itemCertificateId || (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID); tablet.sendToQml({ method: 'inspectionCertificate_setCertificateId', + entityId: currentEntityWithContextOverlay, certificateId: certificateId }); } @@ -584,7 +585,7 @@ var selectionDisplay = null; // for gridTool.js to ignore tablet.gotoHomeScreen(); break; case 'inspectionCertificate_requestOwnershipVerification': - ContextOverlay.requestOwnershipVerification(message.certificateId); + ContextOverlay.requestOwnershipVerification(message.entity); break; case 'inspectionCertificate_showInMarketplaceClicked': tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); From 725eb1416370efac3b40883455be10cce9662fc9 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 7 Feb 2018 16:43:15 -0800 Subject: [PATCH 06/80] Fix for deadlock triggering while loading QML engine --- interface/src/Application.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3e7dd3e223..c22a370b1f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2404,7 +2404,9 @@ void Application::initializeUi() { tabletScriptingInterface->getTablet(SYSTEM_TABLET); } auto offscreenUi = DependencyManager::get(); + DeadlockWatchdogThread::pause(); offscreenUi->create(); + DeadlockWatchdogThread::resume(); auto surfaceContext = offscreenUi->getSurfaceContext(); From d2f5645f96e5b1b36b7258b6952bac4521a9a298 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 7 Feb 2018 15:27:34 -0800 Subject: [PATCH 07/80] Don't trigger a backtrace exception on quitting while in HMD --- interface/src/Application_render.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp index e1f198eed2..5cc072df37 100644 --- a/interface/src/Application_render.cpp +++ b/interface/src/Application_render.cpp @@ -55,7 +55,7 @@ void Application::paintGL() { // If a display plugin loses it's underlying support, it // needs to be able to signal us to not use it if (!displayPlugin->beginFrameRender(_renderFrameCount)) { - updateDisplayMode(); + QMetaObject::invokeMethod(this, "updateDisplayMode"); return; } } From 7eecc4767274a3e1aeba90b5670f963a3f0d7453 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Thu, 8 Feb 2018 10:23:25 -0800 Subject: [PATCH 08/80] Prevent deadlock crashes when building shaders at startup --- interface/src/Application.cpp | 45 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c22a370b1f..be2a54b8e9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -318,7 +318,7 @@ static QTimer pingTimer; static bool DISABLE_WATCHDOG = true; #else static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" }; -static bool DISABLE_WATCHDOG = QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG); +static bool DISABLE_WATCHDOG = nsightActive() || QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG); #endif #if defined(USE_GLES) @@ -415,20 +415,26 @@ public: *crashTrigger = 0xDEAD10CC; } + static void withPause(const std::function& lambda) { + pause(); + lambda(); + resume(); + } static void pause() { _paused = true; } static void resume() { - _paused = false; + // Update the heartbeat BEFORE resuming the checks updateHeartbeat(); + _paused = false; } void run() override { while (!_quit) { QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS); // Don't do heartbeat detection under nsight - if (nsightActive() || _paused) { + if (_paused) { continue; } uint64_t lastHeartbeat = _heartbeat; // sample atomic _heartbeat, because we could context switch away and have it updated on us @@ -2283,29 +2289,22 @@ void Application::initializeGL() { initDisplay(); qCDebug(interfaceapp, "Initialized Display."); -#ifdef Q_OS_OSX - // FIXME: on mac os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread. - DeadlockWatchdogThread::pause(); -#endif - - // Set up the render engine - render::CullFunctor cullFunctor = LODManager::shouldRender; - static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; - _renderEngine->addJob("UpdateScene"); + // FIXME: on low end systems os the shaders take up to 1 minute to compile, so we pause the deadlock watchdog thread. + DeadlockWatchdogThread::withPause([&] { + // Set up the render engine + render::CullFunctor cullFunctor = LODManager::shouldRender; + static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; + _renderEngine->addJob("UpdateScene"); #ifndef Q_OS_ANDROID - _renderEngine->addJob("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED); + _renderEngine->addJob("SecondaryCameraJob", cullFunctor, !DISABLE_DEFERRED); #endif - _renderEngine->addJob("RenderMainView", cullFunctor, !DISABLE_DEFERRED, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0); + _renderEngine->addJob("RenderMainView", cullFunctor, !DISABLE_DEFERRED, render::ItemKey::TAG_BITS_0, render::ItemKey::TAG_BITS_0); + _renderEngine->load(); + _renderEngine->registerScene(_main3DScene); - _renderEngine->load(); - _renderEngine->registerScene(_main3DScene); - - // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. - DependencyManager::get()->initializeShapePipelines(); - -#ifdef Q_OS_OSX - DeadlockWatchdogThread::resume(); -#endif + // Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success. + DependencyManager::get()->initializeShapePipelines(); + }); _offscreenContext = new OffscreenGLCanvas(); _offscreenContext->setObjectName("MainThreadContext"); From 950a62f3f8a88d7a3f0b9e1d05b62c49207990d8 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 8 Feb 2018 15:52:20 -0800 Subject: [PATCH 09/80] Global graphics flag default to on. Added cast shadows flag to zone/keylight. Exit the RenderShadowMap job if current keylight doesn't cast shadows. --- .../src/RenderableZoneEntityItem.cpp | 1 + .../entities/src/EntityItemProperties.cpp | 2 ++ libraries/entities/src/EntityPropertyFlags.h | 9 ++--- .../entities/src/KeyLightPropertyGroup.cpp | 35 +++++++++++++++---- .../entities/src/KeyLightPropertyGroup.h | 2 ++ libraries/graphics/src/graphics/Light.cpp | 9 ++++- libraries/graphics/src/graphics/Light.h | 5 +++ .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 +- .../src/DeferredLightingEffect.cpp | 16 ++++++--- .../render-utils/src/DeferredLightingEffect.h | 2 +- .../render-utils/src/RenderShadowTask.cpp | 15 ++++++++ libraries/render-utils/src/RenderShadowTask.h | 2 +- scripts/system/html/entityProperties.html | 4 +++ scripts/system/html/js/entityProperties.js | 7 ++++ 15 files changed, 94 insertions(+), 20 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 04f07c5bd3..c46409c4ee 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -330,6 +330,7 @@ void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity sunLight->setColor(ColorUtils::toVec3(_keyLightProperties.getColor())); sunLight->setIntensity(_keyLightProperties.getIntensity()); sunLight->setDirection(_keyLightProperties.getDirection()); + sunLight->setCastShadows(_keyLightProperties.getCastShadows()); } void ZoneEntityRenderer::updateAmbientLightFromEntity(const TypedEntityPointer& entity) { diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index e2a5ddf8b5..cca4e858fa 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1128,6 +1128,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_DIRECTION, KeyLightDirection, keyLightDirection, glm::vec3); + ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_CAST_SHADOWS, KeyLightCastShadows, keyLightCastShadows, bool); + ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray); ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 90438ab01c..ffcd4f64cb 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -205,6 +205,11 @@ enum EntityPropertyList { PROP_HAZE_MODE, + PROP_KEYLIGHT_COLOR, + PROP_KEYLIGHT_INTENSITY, + PROP_KEYLIGHT_DIRECTION, + PROP_KEYLIGHT_CAST_SHADOWS, + PROP_HAZE_RANGE, PROP_HAZE_COLOR, PROP_HAZE_GLARE_COLOR, @@ -246,10 +251,6 @@ enum EntityPropertyList { // Aliases/Piggyback properties for Zones. These properties intentionally reuse the enum values for // other properties which will never overlap with each other. We do this so that we don't have to expand // the size of the properties bitflags mask - PROP_KEYLIGHT_COLOR = PROP_COLOR, - PROP_KEYLIGHT_INTENSITY = PROP_INTENSITY, - PROP_KEYLIGHT_DIRECTION = PROP_EXPONENT, - PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, PROP_SKYBOX_URL = PROP_ANIMATION_FPS, diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index c476b4c23c..70b9a5395a 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -21,6 +21,7 @@ const xColor KeyLightPropertyGroup::DEFAULT_KEYLIGHT_COLOR = { 255, 255, 255 }; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_INTENSITY = 1.0f; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY = 0.5f; const glm::vec3 KeyLightPropertyGroup::DEFAULT_KEYLIGHT_DIRECTION = { 0.0f, -1.0f, 0.0f }; +const bool KeyLightPropertyGroup::DEFAULT_KEYLIGHT_CAST_SHADOWS { false }; void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { @@ -28,23 +29,27 @@ void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desired COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_COLOR, KeyLight, keyLight, Color, color); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_INTENSITY, KeyLight, keyLight, Intensity, intensity); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_DIRECTION, KeyLight, keyLight, Direction, direction); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_CAST_SHADOWS, KeyLight, keyLight, CastShadows, castShadows); } void KeyLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, color, xColor, setColor); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, intensity, float, setIntensity); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, direction, glmVec3, setDirection); - + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, castShadows, bool, setCastShadows); + // legacy property support COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightColor, xColor, setColor, getColor); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightIntensity, float, setIntensity, getIntensity); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightDirection, glmVec3, setDirection, getDirection); + COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightCastShadows, bool, setCastShadows, getCastShadows); } void KeyLightPropertyGroup::merge(const KeyLightPropertyGroup& other) { COPY_PROPERTY_IF_CHANGED(color); COPY_PROPERTY_IF_CHANGED(intensity); COPY_PROPERTY_IF_CHANGED(direction); + COPY_PROPERTY_IF_CHANGED(castShadows); } void KeyLightPropertyGroup::debugDump() const { @@ -52,6 +57,7 @@ void KeyLightPropertyGroup::debugDump() const { qCDebug(entities) << " color:" << getColor(); // << "," << getColor()[1] << "," << getColor()[2]; qCDebug(entities) << " intensity:" << getIntensity(); qCDebug(entities) << " direction:" << getDirection(); + qCDebug(entities) << " castShadows:" << getCastShadows(); } void KeyLightPropertyGroup::listChangedProperties(QList& out) { @@ -64,6 +70,9 @@ void KeyLightPropertyGroup::listChangedProperties(QList& out) { if (directionChanged()) { out << "keyLight-direction"; } + if (castShadowsChanged()) { + out << "keyLight-castShadows"; + } } bool KeyLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, @@ -71,19 +80,22 @@ bool KeyLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, int& propertyCount, - OctreeElement::AppendState& appendState) const { + OctreeElement::AppendState& appendState) const +{ bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); - + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, getCastShadows()); + return true; } bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, - int& processedBytes) { + int& processedBytes) +{ int bytesRead = 0; bool overwriteLocalData = true; @@ -92,11 +104,13 @@ bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFl READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); - + READ_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, bool, setCastShadows); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_COLOR, Color); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_INTENSITY, Intensity); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_DIRECTION, Direction); - + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_CAST_SHADOWS, CastShadows); + processedBytes += bytesRead; Q_UNUSED(somethingChanged); @@ -108,6 +122,7 @@ void KeyLightPropertyGroup::markAllChanged() { _colorChanged = true; _intensityChanged = true; _directionChanged = true; + _castShadowsChanged = true; } EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const { @@ -116,7 +131,8 @@ EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_COLOR, color); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_INTENSITY, intensity); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, direction); - + CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_CAST_SHADOWS, castShadows); + return changedProperties; } @@ -124,6 +140,7 @@ void KeyLightPropertyGroup::getProperties(EntityItemProperties& properties) cons COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Color, getColor); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Intensity, getIntensity); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Direction, getDirection); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, CastShadows, getCastShadows); } bool KeyLightPropertyGroup::setProperties(const EntityItemProperties& properties) { @@ -132,6 +149,7 @@ bool KeyLightPropertyGroup::setProperties(const EntityItemProperties& properties SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Color, color, setColor); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Intensity, intensity, setIntensity); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Direction, direction, setDirection); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, CastShadows, castShadows, setCastShadows); return somethingChanged; } @@ -142,6 +160,7 @@ EntityPropertyFlags KeyLightPropertyGroup::getEntityProperties(EncodeBitstreamPa requestedProperties += PROP_KEYLIGHT_COLOR; requestedProperties += PROP_KEYLIGHT_INTENSITY; requestedProperties += PROP_KEYLIGHT_DIRECTION; + requestedProperties += PROP_KEYLIGHT_CAST_SHADOWS; return requestedProperties; } @@ -159,6 +178,7 @@ void KeyLightPropertyGroup::appendSubclassData(OctreePacketData* packetData, Enc APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, getCastShadows()); } int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, @@ -172,6 +192,7 @@ int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); + READ_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, bool, setCastShadows); return bytesRead; } diff --git a/libraries/entities/src/KeyLightPropertyGroup.h b/libraries/entities/src/KeyLightPropertyGroup.h index f33ebb282d..d3c8597f95 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.h +++ b/libraries/entities/src/KeyLightPropertyGroup.h @@ -78,10 +78,12 @@ public: static const float DEFAULT_KEYLIGHT_INTENSITY; static const float DEFAULT_KEYLIGHT_AMBIENT_INTENSITY; static const glm::vec3 DEFAULT_KEYLIGHT_DIRECTION; + static const bool DEFAULT_KEYLIGHT_CAST_SHADOWS; DEFINE_PROPERTY_REF(PROP_KEYLIGHT_COLOR, Color, color, xColor, DEFAULT_KEYLIGHT_COLOR); DEFINE_PROPERTY(PROP_KEYLIGHT_INTENSITY, Intensity, intensity, float, DEFAULT_KEYLIGHT_INTENSITY); DEFINE_PROPERTY_REF(PROP_KEYLIGHT_DIRECTION, Direction, direction, glm::vec3, DEFAULT_KEYLIGHT_DIRECTION); + DEFINE_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, CastShadows, castShadows, bool, DEFAULT_KEYLIGHT_CAST_SHADOWS); }; #endif // hifi_KeyLightPropertyGroup_h diff --git a/libraries/graphics/src/graphics/Light.cpp b/libraries/graphics/src/graphics/Light.cpp index cb5209d4cf..94ec3a376a 100755 --- a/libraries/graphics/src/graphics/Light.cpp +++ b/libraries/graphics/src/graphics/Light.cpp @@ -65,6 +65,14 @@ const Vec3& Light::getDirection() const { return _lightSchemaBuffer->volume.direction; } +void Light::setCastShadows(const bool castShadows) { + _castShadows = castShadows; +} + +const bool Light::getCastShadows() const { + return _castShadows; +} + void Light::setColor(const Color& color) { _lightSchemaBuffer.edit().irradiance.color = color; updateLightRadius(); @@ -132,7 +140,6 @@ void Light::setSpotExponent(float exponent) { _lightSchemaBuffer.edit().irradiance.falloffSpot = exponent; } - void Light::setAmbientIntensity(float intensity) { _ambientSchemaBuffer.edit().intensity = intensity; } diff --git a/libraries/graphics/src/graphics/Light.h b/libraries/graphics/src/graphics/Light.h index 360e3f224e..7497691185 100755 --- a/libraries/graphics/src/graphics/Light.h +++ b/libraries/graphics/src/graphics/Light.h @@ -103,6 +103,9 @@ public: void setDirection(const Vec3& direction); const Vec3& getDirection() const; + void setCastShadows(const bool castShadows); + const bool getCastShadows() const; + void setOrientation(const Quat& orientation); const glm::quat& getOrientation() const { return _transform.getRotation(); } @@ -187,6 +190,8 @@ protected: void updateLightRadius(); + bool _castShadows{ false }; + }; typedef std::shared_ptr< Light > LightPointer; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index c48c6bfc0b..5cafb4caa2 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::SoftEntities); + return static_cast(EntityVersion::ShadowControl); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index d186ed41c3..d996c2826f 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -206,7 +206,8 @@ enum class EntityVersion : PacketVersion { OwnershipChallengeFix, ZoneLightInheritModes = 82, ZoneStageRemoved, - SoftEntities + SoftEntities, + ShadowControl }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index d0d9708c04..c7e6ff1dcf 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -541,15 +541,23 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, auto keyLight = lightAndShadow.first; - graphics::LightPointer keyAmbientLight; + graphics::LightPointer ambientLight; if (lightStage && lightStage->_currentFrame._ambientLights.size()) { - keyAmbientLight = lightStage->getLight(lightStage->_currentFrame._ambientLights.front()); + ambientLight = lightStage->getLight(lightStage->_currentFrame._ambientLights.front()); } - bool hasAmbientMap = (keyAmbientLight != nullptr); + bool hasAmbientMap = (ambientLight != nullptr); // Setup the global directional pass pipeline { - if (deferredLightingEffect->_shadowMapEnabled) { + // Check if keylight casts shadows + bool keyLightCastShadows { false }; + + if (lightStage && lightStage->_currentFrame._sunLights.size()) { + graphics::LightPointer keyLight = lightStage->getLight(lightStage->_currentFrame._sunLights.front()); + keyLightCastShadows = keyLight->getCastShadows(); + } + + if (deferredLightingEffect->_shadowMapEnabled && keyLightCastShadows) { // If the keylight has an ambient Map then use the Skybox version of the pass // otherwise use the ambient sphere version diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 1b776e6409..ce7ecacbbe 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -61,7 +61,7 @@ public: private: DeferredLightingEffect() = default; - bool _shadowMapEnabled{ false }; + bool _shadowMapEnabled{ true }; // note that this value is overwritten in the ::configure method bool _ambientOcclusionEnabled{ false }; graphics::MeshPointer _pointLightMesh; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index e8963c2e4e..c5bdcf03dd 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -120,6 +120,12 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con auto lightStage = renderContext->_scene->getStage(); assert(lightStage); + // Exit if current keylight does not cast shadows + bool castShadows = lightStage->getCurrentKeyLight()->getCastShadows(); + if (!castShadows) { + return; + } + auto shadow = lightStage->getCurrentKeyShadow(); if (!shadow || _cascadeIndex >= shadow->getCascadeCount()) { return; @@ -378,6 +384,15 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, O void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { auto lightStage = renderContext->_scene->getStage(); assert(lightStage); + + // Exit if current keylight does not cast shadows + bool castShadows = lightStage->getCurrentKeyLight()->getCastShadows(); + if (!castShadows) { + output.edit0() = ItemFilter::Builder::nothing(); + output.edit1() = ViewFrustumPointer(); + return; + } + // Cache old render args RenderArgs* args = renderContext->args; diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 7f127a558c..98b70c0c9f 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -38,7 +38,7 @@ class RenderShadowTaskConfig : public render::Task::Config::Persistent { Q_OBJECT Q_PROPERTY(bool enabled MEMBER enabled NOTIFY dirty) public: - RenderShadowTaskConfig() : render::Task::Config::Persistent(QStringList() << "Render" << "Engine" << "Shadows", false) {} + RenderShadowTaskConfig() : render::Task::Config::Persistent(QStringList() << "Render" << "Engine" << "Shadows", true) {} signals: void dirty(); diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index b93974ee77..856ca3c6e1 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -532,6 +532,10 @@

+
+ + +
Skybox diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 7008d0df66..7e8827a9b5 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -652,6 +652,8 @@ function loaded() { var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); + var elZoneKeyLightCastShadows = document.getElementById("property-zone-key-light-cast-shadows"); + // Skybox var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); @@ -1026,6 +1028,8 @@ function loaded() { elZoneKeyLightDirectionX.value = properties.keyLight.direction.x.toFixed(2); elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); + elZoneKeyLightCastShadows.checked = properties.keyLight.castShadows; + // Skybox elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); @@ -1463,6 +1467,9 @@ function loaded() { elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); + elZoneKeyLightCastShadows.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('keyLight', 'castShadows')); + // Skybox var skyboxModeChanged = createZoneComponentModeChangedFunction('skyboxMode', elZoneSkyboxModeInherit, elZoneSkyboxModeDisabled, elZoneSkyboxModeEnabled); From 13ce6bbabd3c979fae0df3de03185ae91dc9c64f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 6 Feb 2018 13:44:59 -0800 Subject: [PATCH 10/80] fix lasers going to origin --- scripts/system/libraries/pointersUtils.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/system/libraries/pointersUtils.js b/scripts/system/libraries/pointersUtils.js index 2af563f8d4..53959b91f8 100644 --- a/scripts/system/libraries/pointersUtils.js +++ b/scripts/system/libraries/pointersUtils.js @@ -30,7 +30,6 @@ Pointer = function(hudLayer, pickType, pointerData) { ignoreRayIntersection: true, // always ignore this drawInFront: !hudLayer, // Even when burried inside of something, show it. drawHUDLayer: hudLayer, - parentID: MyAvatar.SELF_ID }; this.halfEnd = { type: "sphere", @@ -53,7 +52,6 @@ Pointer = function(hudLayer, pickType, pointerData) { ignoreRayIntersection: true, // always ignore this drawInFront: !hudLayer, // Even when burried inside of something, show it. drawHUDLayer: hudLayer, - parentID: MyAvatar.SELF_ID }; this.fullEnd = { type: "sphere", @@ -76,7 +74,6 @@ Pointer = function(hudLayer, pickType, pointerData) { ignoreRayIntersection: true, // always ignore this drawInFront: !hudLayer, // Even when burried inside of something, show it. drawHUDLayer: hudLayer, - parentID: MyAvatar.SELF_ID }; this.renderStates = [ From 7c21db93a3c061b93c2cee6118baf87db2837be3 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 8 Feb 2018 17:08:51 -0800 Subject: [PATCH 11/80] fixing case when grabbed target is destroyed --- .../controllerModules/farActionGrabEntity.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 32bf7316a9..b72a38f986 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -208,7 +208,7 @@ Script.include("/~/system/libraries/Xform.js"); var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, ["position"]); + var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, GRABBABLE_PROPERTIES); var now = Date.now(); var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds this.currentObjectTime = now; @@ -369,6 +369,14 @@ Script.include("/~/system/libraries/Xform.js"); } }; + this.targetIsNull = function() { + var properties = Entities.getEntityProperties(this.grabbedThingID); + if (Object.keys(properties).length === 0 && this.distanceHolding) { + return true; + } + return false; + } + this.isReady = function (controllerData) { if (HMD.active) { if (this.notPointingAtEntity(controllerData)) { @@ -391,7 +399,7 @@ Script.include("/~/system/libraries/Xform.js"); this.run = function (controllerData) { if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || - this.notPointingAtEntity(controllerData)) { + this.notPointingAtEntity(controllerData) || this.targetIsNull()) { this.endNearGrabAction(); return makeRunningValues(false, [], []); } From 6471780e210e52cac921c02cdc1bbafcde4e8575 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 8 Feb 2018 17:17:04 -0800 Subject: [PATCH 12/80] allow overlay children to follow avatar from one domain to another --- interface/src/ui/overlays/Base3DOverlay.cpp | 2 ++ interface/src/ui/overlays/Line3DOverlay.cpp | 2 +- libraries/avatars/src/AvatarData.h | 6 +++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 23e09fe5ca..ff5a202910 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -181,6 +181,8 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { if (properties["parentID"].isValid()) { setParentID(QUuid(properties["parentID"].toString())); + bool success; + getParentPointer(success); // call this to hook-up the parent's back-pointers to its child overlays needRenderItemUpdate = true; } if (properties["parentJointIndex"].isValid()) { diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 7200abf74e..c2e5ad1fb4 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -63,7 +63,7 @@ glm::vec3 Line3DOverlay::getEnd() const { localEnd = getLocalEnd(); worldEnd = localToWorld(localEnd, getParentID(), getParentJointIndex(), getScalesWithParent(), success); if (!success) { - qDebug() << "Line3DOverlay::getEnd failed"; + qDebug() << "Line3DOverlay::getEnd failed, parentID = " << getParentID(); } return worldEnd; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a363fb6d15..f24bd51bde 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -707,7 +707,11 @@ public slots: void setJointMappingsFromNetworkReply(); void setSessionUUID(const QUuid& sessionUUID) { if (sessionUUID != getID()) { - setID(sessionUUID); + if (sessionUUID == QUuid()) { + setID(AVATAR_SELF_ID); + } else { + setID(sessionUUID); + } emit sessionUUIDChanged(); } } From 7ee5245aebeb645c03e2849cd57a235aebb1d96c Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Fri, 9 Feb 2018 10:45:29 -0800 Subject: [PATCH 13/80] Added shadow caster flag to filter. --- libraries/render-utils/src/RenderShadowTask.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index c5bdcf03dd..e34f550def 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -224,7 +224,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto setupOutput = task.addJob("ShadowSetup"); const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene - static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); + static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withShadowCaster(); const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); @@ -398,7 +398,7 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask); + output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withShadowCaster(); // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); From 522c577e732635b43dbed78e4d7868ad7dac5e64 Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 9 Feb 2018 14:08:55 -0800 Subject: [PATCH 14/80] FIxing the bad ambient lighting on scattering surfaces --- libraries/render-utils/src/LightAmbient.slh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render-utils/src/LightAmbient.slh b/libraries/render-utils/src/LightAmbient.slh index 0502446db8..c45f036486 100644 --- a/libraries/render-utils/src/LightAmbient.slh +++ b/libraries/render-utils/src/LightAmbient.slh @@ -78,7 +78,7 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie vec3 ambientSpaceSurfaceNormal = (ambient.transform * vec4(surface.normal, 0.0)).xyz; vec3 ambientSpaceSurfaceEyeDir = (ambient.transform * vec4(surface.eyeDir, 0.0)).xyz; <@if supportScattering@> - vec3 ambientSpaceLowNormalCurvature = (ambient.transform * lowNormalCurvature).xyz; + vec3 ambientSpaceLowNormal = (ambient.transform * vec4(lowNormalCurvature.xyz, 0.0)).xyz; <@endif@> vec3 ambientFresnel = fresnelSchlickAmbient(fresnelF0, surface.ndotv, 1.0-surface.roughness); @@ -99,7 +99,7 @@ void evalLightingAmbient(out vec3 diffuse, out vec3 specular, LightAmbient ambie obscurance = min(obscurance, ambientOcclusion); // Diffuse from ambient - diffuse = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(ambient), ambientSpaceLowNormalCurvature).xyz; + diffuse = sphericalHarmonics_evalSphericalLight(getLightAmbientSphere(ambient), ambientSpaceLowNormal).xyz; // Scattering ambient specular is the same as non scattering for now // TODO: we should use the same specular answer as for direct lighting From 4acd0a34f50ec1219a40ca82cea17df24acf0435 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 12 Feb 2018 12:55:14 -0800 Subject: [PATCH 15/80] Seems OK. Before adding flag to entity. --- libraries/render-utils/src/RenderShadowTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index e34f550def..53c109dc9f 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -224,7 +224,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto setupOutput = task.addJob("ShadowSetup"); const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene - static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withShadowCaster(); + static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); From cf5452313a01c8eab3404a4fccc9f2145d56fae7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 12 Feb 2018 18:44:24 -0800 Subject: [PATCH 16/80] WIP check in of making the use of dq or mat dynamic per model --- .../src/CauterizedMeshPartPayload.cpp | 30 ++++- .../src/CauterizedMeshPartPayload.h | 12 +- .../render-utils/src/CauterizedModel.cpp | 123 +++++++++++------- .../render-utils/src/MeshPartPayload.cpp | 99 ++++++++------ libraries/render-utils/src/MeshPartPayload.h | 20 +-- libraries/render-utils/src/Model.cpp | 65 +++++---- libraries/render-utils/src/Model.h | 11 +- .../render-utils/src/SoftAttachmentModel.cpp | 36 ++--- 8 files changed, 238 insertions(+), 158 deletions(-) diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 3d213840dd..e7ea902adb 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -20,16 +20,32 @@ using namespace render; CauterizedMeshPartPayload::CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform) : ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform) {} -void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector& clusterTransforms, const std::vector& cauterizedClusterTransforms) { - ModelMeshPartPayload::updateClusterBuffer(clusterTransforms); +void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices, + const std::vector& cauterizedClusterMatrices) { + ModelMeshPartPayload::updateClusterBuffer(clusterMatrices); - if (cauterizedClusterTransforms.size() > 1) { + if (cauterizedClusterMatrices.size() > 1) { if (!_cauterizedClusterBuffer) { - _cauterizedClusterBuffer = std::make_shared(cauterizedClusterTransforms.size() * sizeof(TransformType), - (const gpu::Byte*) cauterizedClusterTransforms.data()); + _cauterizedClusterBuffer = std::make_shared(cauterizedClusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) cauterizedClusterMatrices.data()); } else { - _cauterizedClusterBuffer->setSubData(0, cauterizedClusterTransforms.size() * sizeof(TransformType), - (const gpu::Byte*) cauterizedClusterTransforms.data()); + _cauterizedClusterBuffer->setSubData(0, cauterizedClusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) cauterizedClusterMatrices.data()); + } + } +} + +void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector& clusterDualQuaternions, + const std::vector& cauterizedClusterDualQuaternions) { + ModelMeshPartPayload::updateClusterBuffer(clusterDualQuaternions); + + if (cauterizedClusterDualQuaternions.size() > 1) { + if (!_cauterizedClusterBuffer) { + _cauterizedClusterBuffer = std::make_shared(cauterizedClusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion), + (const gpu::Byte*) cauterizedClusterDualQuaternions.data()); + } else { + _cauterizedClusterBuffer->setSubData(0, cauterizedClusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion), + (const gpu::Byte*) cauterizedClusterDualQuaternions.data()); } } } diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h index 2337632047..3783bd1bf8 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.h +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h @@ -15,13 +15,13 @@ class CauterizedMeshPartPayload : public ModelMeshPartPayload { public: CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); -#if defined(SKIN_DQ) - using TransformType = Model::TransformDualQuaternion; -#else - using TransformType = glm::mat4; -#endif + // matrix palette skinning + void updateClusterBuffer(const std::vector& clusterMatrices, + const std::vector& cauterizedClusterMatrices); - void updateClusterBuffer(const std::vector& clusterTransforms, const std::vector& cauterizedClusterTransforms); + // dual quaternion skinning + void updateClusterBuffer(const std::vector& clusterDualQuaternions, + const std::vector& cauterizedClusterQuaternions); void updateTransformForCauterizedMesh(const Transform& renderTransform); diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 54dfd96a00..f4a745278e 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -35,8 +35,13 @@ bool CauterizedModel::updateGeometry() { const FBXGeometry& fbxGeometry = getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { Model::MeshState state; - state.clusterTransforms.resize(mesh.clusters.size()); - _cauterizeMeshStates.append(state); + if (_useDualQuaternionSkinning) { + state.clusterDualQuaternions.resize(mesh.clusters.size()); + _cauterizeMeshStates.append(state); + } else { + state.clusterMatrices.resize(mesh.clusters.size()); + _cauterizeMeshStates.append(state); + } } } return needsFullUpdate; @@ -109,33 +114,33 @@ void CauterizedModel::updateClusterMatrices() { const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); -#if defined(SKIN_DQ) - auto jointPose = _rig.getJointPose(cluster.jointIndex); - Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); - Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); - state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); - state.clusterTransforms[j].setCauterizationParameters(0.0f, jointPose.trans()); -#else - auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); -#endif + if (_useDualQuaternionSkinning) { + auto jointPose = _rig.getJointPose(cluster.jointIndex); + Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); + Transform clusterTransform; + Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); + state.clusterDualQuaternions[j].setCauterizationParameters(0.0f, jointPose.trans()); + } else { + auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + } } } // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty. if (!_cauterizeBoneSet.empty()) { -#if defined(SKIN_DQ) + AnimPose cauterizePose = _rig.getJointPose(geometry.neckJointIndex); cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f); -#else + static const glm::mat4 zeroScale( glm::vec4(0.0001f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 0.0001f, 0.0f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0001f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); auto cauterizeMatrix = _rig.getJointTransform(geometry.neckJointIndex) * zeroScale; -#endif + for (int i = 0; i < _cauterizeMeshStates.size(); i++) { Model::MeshState& state = _cauterizeMeshStates[i]; const FBXMesh& mesh = geometry.meshes.at(i); @@ -143,19 +148,24 @@ void CauterizedModel::updateClusterMatrices() { for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); - if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { - // not cauterized so just copy the value from the non-cauterized version. - state.clusterTransforms[j] = _meshStates[i].clusterTransforms[j]; + if (_useDualQuaternionSkinning) { + if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { + // not cauterized so just copy the value from the non-cauterized version. + state.clusterDualQuaternions[j] = _meshStates[i].clusterDualQuaternions[j]; + } else { + Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); + Transform clusterTransform; + Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); + state.clusterDualQuaternions[j].setCauterizationParameters(1.0f, cauterizePose.trans()); + } } else { -#if defined(SKIN_DQ) - Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans()); - Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); - state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); - state.clusterTransforms[j].setCauterizationParameters(1.0f, cauterizePose.trans()); -#else - glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); -#endif + if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) { + // not cauterized so just copy the value from the non-cauterized version. + state.clusterMatrices[j] = _meshStates[i].clusterMatrices[j]; + } else { + glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + } } } } @@ -213,38 +223,51 @@ void CauterizedModel::updateRenderItems() { auto itemID = self->_modelMeshRenderItemIDs[i]; auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; - auto clusterTransforms(self->getMeshState(meshIndex).clusterTransforms); - auto clusterTransformsCauterized(self->getCauterizeMeshState(meshIndex).clusterTransforms); + + const auto& meshState = self->getMeshState(meshIndex); + const auto& cauterizedMeshState = self->getCauterizeMeshState(meshIndex); bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); - transaction.updateItem(itemID, [modelTransform, clusterTransforms, clusterTransformsCauterized, invalidatePayloadShapeKey, + transaction.updateItem(itemID, [modelTransform, meshState, cauterizedMeshState, invalidatePayloadShapeKey, isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) { - data.updateClusterBuffer(clusterTransforms, clusterTransformsCauterized); + if (_useDualQuaternionSkinning) { + data.updateClusterBuffer(meshState.clusterDualQuaternions, + cauterizedMeshState.clusterDualQuaternions); + } else { + data.updateClusterBuffer(meshState.clusterMatrices, + cauterizedMeshState.clusterMatrices); + } Transform renderTransform = modelTransform; - if (clusterTransforms.size() == 1) { -#if defined(SKIN_DQ) - Transform transform(clusterTransforms[0].getRotation(), - clusterTransforms[0].getScale(), - clusterTransforms[0].getTranslation()); - renderTransform = modelTransform.worldTransform(transform); -#else - renderTransform = modelTransform.worldTransform(Transform(clusterTransforms[0])); -#endif + if (_useDualQuaternionSkinning) { + if (meshState.clusterDualQuaternions.size() == 1) { + const auto& dq = meshState.clusterDualQuaternions[0]; + Transform transform(dq.getRotation(), + dq.getScale(), + dq.getTranslation()); + renderTransform = modelTransform.worldTransform(transform); + } + } else { + if (meshState.clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); + } } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); renderTransform = modelTransform; - if (clusterTransformsCauterized.size() == 1) { -#if defined(SKIN_DQ) - Transform transform(clusterTransformsCauterized[0].getRotation(), - clusterTransformsCauterized[0].getScale(), - clusterTransformsCauterized[0].getTranslation()); - renderTransform = modelTransform.worldTransform(Transform(transform)); -#else - renderTransform = modelTransform.worldTransform(Transform(clusterTransformsCauterized[0])); -#endif + if (_useDualQuaternionSkinning) { + if (cauterizedMeshState.clusterDualQuaternions.size() == 1) { + const auto& dq = cauterizedMeshState.clusterDualQuaternions[0]; + Transform transform(dq.getRotation(), + dq.getScale(), + dq.getTranslation()); + renderTransform = modelTransform.worldTransform(Transform(transform)); + } + } else { + if (cauterizedMeshState.clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(cauterizedMeshState.clusterMatrices[0])); + } } data.updateTransformForCauterizedMesh(renderTransform); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 9655b60a78..595a4013f1 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -340,20 +340,27 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in const Model::MeshState& state = model->getMeshState(_meshIndex); updateMeshPart(modelMesh, partIndex); - computeAdjustedLocalBound(state.clusterTransforms); + + if (_useDualQuaternionSkinning) { + computeAdjustedLocalBound(state.clusterDualQuaternions); + } else { + computeAdjustedLocalBound(state.clusterMatrices); + } updateTransform(transform, offsetTransform); Transform renderTransform = transform; - if (state.clusterTransforms.size() == 1) { -#if defined(SKIN_DQ) - Transform transform(state.clusterTransforms[0].getRotation(), - state.clusterTransforms[0].getScale(), - state.clusterTransforms[0].getTranslation()); - renderTransform = transform.worldTransform(Transform(transform)); -#else - renderTransform = transform.worldTransform(Transform(state.clusterTransforms[0])); -#endif - + if (_useDualQuaternionSkinning) { + if (state.clusterDualQuaternions.size() == 1) { + const auto& dq = state.clusterDualQuaternions[0]; + Transform transform(dq.getRotation(), + dq.getScale(), + dq.getTranslation()); + renderTransform = transform.worldTransform(Transform(transform)); + } + } else { + if (state.clusterMatrices.size() == 1) { + renderTransform = transform.worldTransform(Transform(state.clusterMatrices[0])); + } } updateTransformForSkinnedMesh(renderTransform, transform); @@ -383,16 +390,30 @@ void ModelMeshPartPayload::notifyLocationChanged() { } -void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterTransforms) { +void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices) { // Once computed the cluster matrices, update the buffer(s) - if (clusterTransforms.size() > 1) { + if (clusterMatrices.size() > 1) { if (!_clusterBuffer) { - _clusterBuffer = std::make_shared(clusterTransforms.size() * sizeof(TransformType), - (const gpu::Byte*) clusterTransforms.data()); + _clusterBuffer = std::make_shared(clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) clusterMatrices.data()); } else { - _clusterBuffer->setSubData(0, clusterTransforms.size() * sizeof(TransformType), - (const gpu::Byte*) clusterTransforms.data()); + _clusterBuffer->setSubData(0, clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) clusterMatrices.data()); + } + } +} + +void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterDualQuaternions) { + // Once computed the cluster matrices, update the buffer(s) + if (clusterDualQuaternions.size() > 1) { + if (!_clusterBuffer) { + _clusterBuffer = std::make_shared(clusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion), + (const gpu::Byte*) clusterDualQuaternions.data()); + } + else { + _clusterBuffer->setSubData(0, clusterDualQuaternions.size() * sizeof(Model::TransformDualQuaternion), + (const gpu::Byte*) clusterDualQuaternions.data()); } } } @@ -550,29 +571,33 @@ void ModelMeshPartPayload::render(RenderArgs* args) { args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE; } - -void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterTransforms) { +void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterMatrices) { _adjustedLocalBound = _localBound; - if (clusterTransforms.size() > 0) { -#if defined(SKIN_DQ) - Transform rootTransform(clusterTransforms[0].getRotation(), - clusterTransforms[0].getScale(), - clusterTransforms[0].getTranslation()); - _adjustedLocalBound.transform(rootTransform); -#else - _adjustedLocalBound.transform(clusterTransforms[0]); -#endif + if (clusterMatrices.size() > 0) { + _adjustedLocalBound.transform(clusterMatrices[0]); - for (int i = 1; i < (int)clusterTransforms.size(); ++i) { + for (int i = 1; i < (int)clusterMatrices.size(); ++i) { AABox clusterBound = _localBound; -#if defined(SKIN_DQ) - Transform transform(clusterTransforms[i].getRotation(), - clusterTransforms[i].getScale(), - clusterTransforms[i].getTranslation()); - clusterBound.transform(transform); -#else - clusterBound.transform(clusterTransforms[i]); -#endif + clusterBound.transform(clusterMatrices[i]); + _adjustedLocalBound += clusterBound; + } + } +} + +void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterDualQuaternions) { + _adjustedLocalBound = _localBound; + if (clusterDualQuaternions.size() > 0) { + Transform rootTransform(clusterDualQuaternions[0].getRotation(), + clusterDualQuaternions[0].getScale(), + clusterDualQuaternions[0].getTranslation()); + _adjustedLocalBound.transform(rootTransform); + + for (int i = 1; i < (int)clusterDualQuaternions.size(); ++i) { + AABox clusterBound = _localBound; + Transform transform(clusterDualQuaternions[i].getRotation(), + clusterDualQuaternions[i].getScale(), + clusterDualQuaternions[i].getTranslation()); + clusterBound.transform(transform); _adjustedLocalBound += clusterBound; } } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 21f9dc2e68..7791390203 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -93,14 +93,14 @@ public: void notifyLocationChanged() override; -#if defined(SKIN_DQ) - using TransformType = Model::TransformDualQuaternion; -#else - using TransformType = glm::mat4; -#endif - void updateKey(bool isVisible, bool isLayered, uint8_t tagBits) override; - void updateClusterBuffer(const std::vector& clusterTransforms); + + // matrix palette skinning + void updateClusterBuffer(const std::vector& clusterMatrices); + + // dual quaternion skinning + void updateClusterBuffer(const std::vector& clusterDualQuaternions); + void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); // Render Item interface @@ -115,7 +115,11 @@ public: void bindMesh(gpu::Batch& batch) override; void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; - void computeAdjustedLocalBound(const std::vector& clusterTransforms); + // matrix palette skinning + void computeAdjustedLocalBound(const std::vector& clusterMatrices); + + // dual quaternion skinning + void computeAdjustedLocalBound(const std::vector& clusterDualQuaternions); gpu::BufferPointer _clusterBuffer; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b9ccc28c01..92abac3520 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -277,26 +277,35 @@ void Model::updateRenderItems() { auto itemID = self->_modelMeshRenderItemIDs[i]; auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; - auto clusterTransforms(self->getMeshState(meshIndex).clusterTransforms); + + const auto& meshState = self->getMeshState(meshIndex); bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); - transaction.updateItem(itemID, [modelTransform, clusterTransforms, + transaction.updateItem(itemID, [modelTransform, meshState, invalidatePayloadShapeKey, isWireframe, isVisible, viewTagBits, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { - data.updateClusterBuffer(clusterTransforms); + if (_useDualQuaternions) { + data.updateClusterBuffer(meshState.clusterDualQuaternions); + } else { + data.updateClusterBuffer(meshState.clusterMatrices); + } Transform renderTransform = modelTransform; - if (clusterTransforms.size() == 1) { -#if defined(SKIN_DQ) - Transform transform(clusterTransforms[0].getRotation(), - clusterTransforms[0].getScale(), - clusterTransforms[0].getTranslation()); - renderTransform = modelTransform.worldTransform(Transform(transform)); -#else - renderTransform = modelTransform.worldTransform(Transform(clusterTransforms[0])); -#endif + + if (_useDualQuaternions) { + if (meshState.clusterDualQuaternions.size() == 1) { + const auto& dq = meshState.clusterDualQuaternions[0]; + Transform transform(dq.getRotation(), + dq.getScale(), + dq.getTranslation()); + renderTransform = modelTransform.worldTransform(Transform(transform)); + } + } else { + if (meshState.clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(meshState.clusterMatrices[0])); + } } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); @@ -377,7 +386,11 @@ bool Model::updateGeometry() { const FBXGeometry& fbxGeometry = getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; - state.clusterTransforms.resize(mesh.clusters.size()); + if (_useDualQuaternions) { + state.clusterDualQuaternions.resize(mesh.clusters.size()); + } else { + state.clusterMatrices.resize(mesh.clusters.size()); + } _meshStates.push_back(state); // Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index @@ -1262,7 +1275,11 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { void Model::computeMeshPartLocalBounds() { for (auto& part : _modelMeshRenderItems) { const Model::MeshState& state = _meshStates.at(part->_meshIndex); - part->computeAdjustedLocalBound(state.clusterTransforms); + if (_useDualQuaternions) { + part->computeAdjustedLocalBound(state.clusterDualQuaternions); + } else { + part->computeAdjustedLocalBound(state.clusterMatrices); + } } } @@ -1281,16 +1298,16 @@ void Model::updateClusterMatrices() { const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); -#if defined(SKIN_DQ) - auto jointPose = _rig.getJointPose(cluster.jointIndex); - Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); - Transform clusterTransform; - Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); - state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform); -#else - auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); -#endif + if (_useDualQuaternionSkinning) { + auto jointPose = _rig.getJointPose(cluster.jointIndex); + Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans()); + Transform clusterTransform; + Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform); + state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(clusterTransform); + } else { + auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + } } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index ca0904f334..84d7dcb7cc 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -254,8 +254,6 @@ public: int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; } bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; } - -#if defined(SKIN_DQ) class TransformDualQuaternion { public: TransformDualQuaternion() {} @@ -293,15 +291,11 @@ public: DualQuaternion _dq; glm::vec4 _cauterizedPosition { 0.0f, 0.0f, 0.0f, 1.0f }; }; -#endif class MeshState { public: -#if defined(SKIN_DQ) - std::vector clusterTransforms; -#else - std::vector clusterTransforms; -#endif + std::vector clusterDualQuaternions; + std::vector clusterMatrices; }; const MeshState& getMeshState(int index) { return _meshStates.at(index); } @@ -420,6 +414,7 @@ protected: virtual void createCollisionRenderItemSet(); bool _isWireframe; + bool _useDualQuaternionSkinning { false }; // debug rendering support int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID; diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 0d0db7cbe3..079e6f75ef 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -52,27 +52,27 @@ void SoftAttachmentModel::updateClusterMatrices() { // TODO: cache these look-ups as an optimization int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); -#if defined(SKIN_DQ) - glm::mat4 jointMatrix; - if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { - jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); - } else { - jointMatrix = _rig.getJointTransform(cluster.jointIndex); - } + if (_useDualQuaternionSkinning) { + glm::mat4 jointMatrix; + if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { + jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); + } else { + jointMatrix = _rig.getJointTransform(cluster.jointIndex); + } - glm::mat4 m; - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m); - state.clusterTransforms[j] = Model::TransformDualQuaternion(m); -#else - glm::mat4 jointMatrix; - if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { - jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); + glm::mat4 m; + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m); + state.clusterDualQuaternions[j] = Model::TransformDualQuaternion(m); } else { - jointMatrix = _rig.getJointTransform(cluster.jointIndex); - } + glm::mat4 jointMatrix; + if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) { + jointMatrix = _rigOverride.getJointTransform(jointIndexOverride); + } else { + jointMatrix = _rig.getJointTransform(cluster.jointIndex); + } - glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]); -#endif + glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); + } } } From 7f5f48bca9ad7a3a9f85934888da314d5f91720a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 13 Feb 2018 11:37:14 -0800 Subject: [PATCH 17/80] Models can now switch between dual quats and matrix palette skinning. but not dynamically, because we still only compile one version of the shader. --- libraries/render-utils/src/CauterizedModel.cpp | 9 +++++---- libraries/render-utils/src/MeshPartPayload.cpp | 3 +++ libraries/render-utils/src/MeshPartPayload.h | 1 + libraries/render-utils/src/Model.cpp | 11 ++++++----- libraries/render-utils/src/Model.h | 3 ++- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index f4a745278e..fb1d31d273 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -228,10 +228,11 @@ void CauterizedModel::updateRenderItems() { const auto& cauterizedMeshState = self->getCauterizeMeshState(meshIndex); bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); + bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); - transaction.updateItem(itemID, [modelTransform, meshState, cauterizedMeshState, invalidatePayloadShapeKey, + transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, cauterizedMeshState, invalidatePayloadShapeKey, isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) { - if (_useDualQuaternionSkinning) { + if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions, cauterizedMeshState.clusterDualQuaternions); } else { @@ -240,7 +241,7 @@ void CauterizedModel::updateRenderItems() { } Transform renderTransform = modelTransform; - if (_useDualQuaternionSkinning) { + if (useDualQuaternionSkinning) { if (meshState.clusterDualQuaternions.size() == 1) { const auto& dq = meshState.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), @@ -256,7 +257,7 @@ void CauterizedModel::updateRenderItems() { data.updateTransformForSkinnedMesh(renderTransform, modelTransform); renderTransform = modelTransform; - if (_useDualQuaternionSkinning) { + if (useDualQuaternionSkinning) { if (cauterizedMeshState.clusterDualQuaternions.size() == 1) { const auto& dq = cauterizedMeshState.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 595a4013f1..1585a075f5 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -335,6 +335,9 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in _shapeID(shapeIndex) { assert(model && model->isLoaded()); + + _useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); + _blendedVertexBuffer = model->_blendedVertexBuffers[_meshIndex]; auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); const Model::MeshState& state = model->getMeshState(_meshIndex); diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 7791390203..220a0bc48c 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -136,6 +136,7 @@ private: gpu::BufferPointer _blendedVertexBuffer; render::ShapeKey _shapeKey { render::ShapeKey::Builder::invalid() }; int _layer { render::Item::LAYER_3D }; + bool _useDualQuaternionSkinning { false }; }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 92abac3520..1318299f43 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -281,12 +281,13 @@ void Model::updateRenderItems() { const auto& meshState = self->getMeshState(meshIndex); bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); + bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); - transaction.updateItem(itemID, [modelTransform, meshState, + transaction.updateItem(itemID, [modelTransform, meshState, useDualQuaternionSkinning, invalidatePayloadShapeKey, isWireframe, isVisible, viewTagBits, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) { - if (_useDualQuaternions) { + if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); } else { data.updateClusterBuffer(meshState.clusterMatrices); @@ -294,7 +295,7 @@ void Model::updateRenderItems() { Transform renderTransform = modelTransform; - if (_useDualQuaternions) { + if (useDualQuaternionSkinning) { if (meshState.clusterDualQuaternions.size() == 1) { const auto& dq = meshState.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), @@ -386,7 +387,7 @@ bool Model::updateGeometry() { const FBXGeometry& fbxGeometry = getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; - if (_useDualQuaternions) { + if (_useDualQuaternionSkinning) { state.clusterDualQuaternions.resize(mesh.clusters.size()); } else { state.clusterMatrices.resize(mesh.clusters.size()); @@ -1275,7 +1276,7 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { void Model::computeMeshPartLocalBounds() { for (auto& part : _modelMeshRenderItems) { const Model::MeshState& state = _meshStates.at(part->_meshIndex); - if (_useDualQuaternions) { + if (_useDualQuaternionSkinning) { part->computeAdjustedLocalBound(state.clusterDualQuaternions); } else { part->computeAdjustedLocalBound(state.clusterMatrices); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 84d7dcb7cc..46dbc90324 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -311,6 +311,7 @@ public: Q_INVOKABLE MeshProxyList getMeshes() const; void scaleToFit(); + bool getUseDualQuaternionSkinning() const { return _useDualQuaternionSkinning; } public slots: void loadURLFinished(bool success); @@ -414,7 +415,7 @@ protected: virtual void createCollisionRenderItemSet(); bool _isWireframe; - bool _useDualQuaternionSkinning { false }; + bool _useDualQuaternionSkinning { true }; // debug rendering support int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID; From 23a29b8d4b18b07f44a667542f20c210131be1d8 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 13 Feb 2018 16:16:04 -0800 Subject: [PATCH 18/80] Added don't castr shadow flag to entity and shape classes. --- libraries/entities/src/EntityItemProperties.h | 1 + .../src/EntityItemPropertiesDefaults.h | 1 + libraries/entities/src/EntityPropertyFlags.h | 1 + libraries/entities/src/ModelEntityItem.cpp | 35 +++++++++++++++++-- libraries/entities/src/ModelEntityItem.h | 5 +++ libraries/entities/src/ShapeEntityItem.cpp | 29 +++++++++++++++ libraries/entities/src/ShapeEntityItem.h | 5 +++ .../render-utils/src/RenderShadowTask.cpp | 4 +-- libraries/render/src/render/Item.h | 4 +++ 9 files changed, 80 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 3e0770f386..731bb9390e 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -128,6 +128,7 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); + DEFINE_PROPERTY(PROP_DONT_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_DONT_CAST_SHADOW); DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor, particle::DEFAULT_COLOR); DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor, particle::DEFAULT_COLOR_SPREAD); DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, xColor, particle::DEFAULT_COLOR); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index eb09a64628..181ba9bbf2 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -46,6 +46,7 @@ const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; +const bool ENTITY_ITEM_DEFAULT_DONT_CAST_SHADOW { false }; const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); const quint64 ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP = 0; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index ffcd4f64cb..fbc4199097 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -31,6 +31,7 @@ enum EntityPropertyList { PROP_SCRIPT, // these properties are supported by some derived classes + PROP_DONT_CAST_SHADOW, PROP_COLOR, // these are used by models only diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 5d33e4c047..73cf29bce1 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -53,6 +53,8 @@ void ModelEntityItem::setTextures(const QString& textures) { EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getModelURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); @@ -64,6 +66,7 @@ EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredP COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslations, getJointTranslations); COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints); _animationProperties.getProperties(properties); + return properties; } @@ -71,6 +74,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); @@ -112,6 +116,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, const unsigned char* dataAt = data; bool animationPropertiesChanged = false; + READ_ENTITY_PROPERTY(PROP_DONT_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); @@ -148,6 +153,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_DONT_CAST_SHADOW; requestedProperties += PROP_MODEL_URL; requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_TEXTURES; @@ -172,6 +178,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_DONT_CAST_SHADOW, getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, getModelURL()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); @@ -191,8 +198,6 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, getJointTranslations()); } - - // added update function back for property fix void ModelEntityItem::update(const quint64& now) { @@ -290,6 +295,7 @@ void ModelEntityItem::updateFrameCount() { } void ModelEntityItem::debugDump() const { + qCDebug(entities) << " can cast shadow" << getCanCastShadow(); qCDebug(entities) << "ModelEntityItem id:" << getEntityItemID(); qCDebug(entities) << " edited ago:" << getEditedAgo(); qCDebug(entities) << " position:" << getWorldPosition(); @@ -571,15 +577,16 @@ QVector ModelEntityItem::getJointTranslationsSet() const { return result; } - xColor ModelEntityItem::getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } + bool ModelEntityItem::hasModel() const { return resultWithReadLock([&] { return !_modelURL.isEmpty(); }); } + bool ModelEntityItem::hasCompoundShapeURL() const { return !_compoundShapeURL.get().isEmpty(); } @@ -722,3 +729,25 @@ bool ModelEntityItem::isAnimatingSomething() const { (_animationProperties.getFPS() != 0.0f); }); } + +bool ModelEntityItem::getCanCastShadow() const { + bool result; + withReadLock([&] { + result = _canCastShadow; + }); + return result; +} + +void ModelEntityItem::setCanCastShadow(bool value) { + bool changed = false; + withWriteLock([&] { + if (_canCastShadow != value) { + changed = true; + _canCastShadow = value; + } + }); + + if (changed) { + emit requestRenderUpdate(); + } +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index c2109ba51f..ec65876d84 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -131,6 +131,9 @@ public: QVector getJointTranslations() const; QVector getJointTranslationsSet() const; + bool getCanCastShadow() const; + void setCanCastShadow(bool value); + private: void setAnimationSettings(const QString& value); // only called for old bitstream format ShapeType computeTrueShapeType() const; @@ -171,6 +174,8 @@ protected: ShapeType _shapeType = SHAPE_TYPE_NONE; + bool _canCastShadow{ ENTITY_ITEM_DEFAULT_DONT_CAST_SHADOW }; + private: uint64_t _lastAnimated{ 0 }; AnimationPropertyGroup _previousAnimationProperties; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index cbcfcaaa1d..2fbb6702f2 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -91,6 +91,8 @@ EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredP EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class properties.setColor(getXColor()); properties.setShape(entity::stringFromShape(getShape())); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); + return properties; } @@ -129,6 +131,7 @@ bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); if (somethingChanged) { bool wantDebug = false; @@ -154,6 +157,7 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape); READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + READ_ENTITY_PROPERTY(PROP_DONT_CAST_SHADOW, bool, setCanCastShadow); return bytesRead; } @@ -165,6 +169,8 @@ EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_SHAPE; requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; + requestedProperties += PROP_DONT_CAST_SHADOW; + return requestedProperties; } @@ -180,6 +186,7 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + APPEND_ENTITY_PROPERTY(PROP_DONT_CAST_SHADOW, getCanCastShadow()); } void ShapeEntityItem::setColor(const rgbColor& value) { @@ -259,6 +266,7 @@ void ShapeEntityItem::debugDump() const { qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); qCDebug(entities) << "SHAPE EntityItem Ptr:" << this; + qCDebug(entities) << " can cast shadow" << getCanCastShadow(); } void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { @@ -362,3 +370,24 @@ ShapeType ShapeEntityItem::getShapeType() const { return _collisionShapeType; } +bool ShapeEntityItem::getCanCastShadow() const { + bool result; + withReadLock([&] { + result = _canCastShadow; + }); + return result; +} + +void ShapeEntityItem::setCanCastShadow(bool value) { + bool changed = false; + withWriteLock([&] { + if (_canCastShadow != value) { + changed = true; + _canCastShadow = value; + } + }); + + if (changed) { + emit requestRenderUpdate(); + } +} diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 84ce1ce57e..63c4eb58c5 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -101,6 +101,9 @@ public: virtual void computeShapeInfo(ShapeInfo& info) override; virtual ShapeType getShapeType() const override; + bool getCanCastShadow() const; + void setCanCastShadow(bool value); + protected: float _alpha { 1 }; @@ -111,6 +114,8 @@ protected: //! prior functionality where new or unsupported shapes are treated as //! ellipsoids. ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; + + bool _canCastShadow { ENTITY_ITEM_DEFAULT_DONT_CAST_SHADOW }; }; #endif // hifi_ShapeEntityItem_h diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 53c109dc9f..eef641e369 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -224,7 +224,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto setupOutput = task.addJob("ShadowSetup"); const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene - static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); + static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withNoShadowCaster(); const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); @@ -398,7 +398,7 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withShadowCaster(); + output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withNoShadowCaster(); // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index ff4b3a0458..7713c1ea7e 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -120,7 +120,10 @@ public: Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); } Builder& withDeformed() { _flags.set(DEFORMED); return (*this); } Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); } + + Builder& withNoShadowCaster() { _flags.reset(SHADOW_CASTER); return (*this); } Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } + Builder& withLayered() { _flags.set(LAYERED); return (*this); } Builder& withTag(Tag tag) { _flags.set(FIRST_TAG_BIT + tag); return (*this); } @@ -155,6 +158,7 @@ public: bool isInvisible() const { return _flags[INVISIBLE]; } bool isShadowCaster() const { return _flags[SHADOW_CASTER]; } + bool isNotShadowCaster() const { return !_flags[SHADOW_CASTER]; } bool isLayered() const { return _flags[LAYERED]; } bool isSpatial() const { return !isLayered(); } From d2c199104e715b33b4ef59922c1e52a010e425b7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 13 Feb 2018 17:47:49 -0800 Subject: [PATCH 19/80] Avatars use Dual Quaternion Skinning and Model Entities do not. --- .../src/avatars-renderer/SkeletonModel.cpp | 2 + .../render-utils/src/MeshPartPayload.cpp | 4 + libraries/render-utils/src/MeshPartPayload.h | 2 +- libraries/render-utils/src/Model.h | 2 +- .../render-utils/src/RenderPipelines.cpp | 79 ++++++++++++++++++- libraries/render-utils/src/Skinning.slh | 14 ++-- libraries/render-utils/src/skin_model.slv | 1 + libraries/render-utils/src/skin_model_dq.slv | 52 ++++++++++++ .../render-utils/src/skin_model_fade.slv | 1 + .../render-utils/src/skin_model_fade_dq.slv | 54 +++++++++++++ .../src/skin_model_normal_map.slv | 1 + .../src/skin_model_normal_map_dq.slv | 61 ++++++++++++++ .../src/skin_model_normal_map_fade.slv | 1 + .../src/skin_model_normal_map_fade_dq.slv | 61 ++++++++++++++ .../render-utils/src/skin_model_shadow.slv | 1 + .../render-utils/src/skin_model_shadow_dq.slv | 30 +++++++ .../src/skin_model_shadow_fade.slv | 1 + .../src/skin_model_shadow_fade_dq.slv | 33 ++++++++ libraries/render/src/render/ShapePipeline.h | 5 ++ 19 files changed, 392 insertions(+), 13 deletions(-) create mode 100644 libraries/render-utils/src/skin_model_dq.slv create mode 100644 libraries/render-utils/src/skin_model_fade_dq.slv create mode 100644 libraries/render-utils/src/skin_model_normal_map_dq.slv create mode 100644 libraries/render-utils/src/skin_model_normal_map_fade_dq.slv create mode 100644 libraries/render-utils/src/skin_model_shadow_dq.slv create mode 100644 libraries/render-utils/src/skin_model_shadow_fade_dq.slv diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 1112ccde60..b2a494230b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -31,6 +31,8 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : _defaultEyeModelPosition(glm::vec3(0.0f, 0.0f, 0.0f)), _headClipDistance(DEFAULT_NEAR_CLIP) { + // SkeletonModels, and by extention Avatars, use Dual Quaternion skinning. + _useDualQuaternionSkinning = true; assert(_owningAvatar); } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 1585a075f5..da3a6d80dd 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -516,6 +516,10 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe if (isWireframe) { builder.withWireframe(); } + if (_useDualQuaternionSkinning) { + builder.withDualQuatSkinned(); + } + _shapeKey = builder.build(); } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 220a0bc48c..cd4d390b1e 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -129,6 +129,7 @@ public: bool _isSkinned{ false }; bool _isBlendShaped { false }; bool _hasTangents { false }; + bool _useDualQuaternionSkinning { false }; private: void initCache(const ModelPointer& model); @@ -136,7 +137,6 @@ private: gpu::BufferPointer _blendedVertexBuffer; render::ShapeKey _shapeKey { render::ShapeKey::Builder::invalid() }; int _layer { render::Item::LAYER_3D }; - bool _useDualQuaternionSkinning { false }; }; namespace render { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 46dbc90324..9aa4aa6b97 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -415,7 +415,7 @@ protected: virtual void createCollisionRenderItemSet(); bool _isWireframe; - bool _useDualQuaternionSkinning { true }; + bool _useDualQuaternionSkinning { false }; // debug rendering support int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID; diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index ad7409b731..68c1918044 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -26,6 +26,8 @@ #include "model_lightmap_normal_map_vert.h" #include "skin_model_vert.h" #include "skin_model_normal_map_vert.h" +#include "skin_model_dq_vert.h" +#include "skin_model_normal_map_dq_vert.h" #include "model_lightmap_fade_vert.h" #include "model_lightmap_normal_map_fade_vert.h" @@ -33,6 +35,8 @@ #include "model_translucent_normal_map_vert.h" #include "skin_model_fade_vert.h" #include "skin_model_normal_map_fade_vert.h" +#include "skin_model_fade_dq_vert.h" +#include "skin_model_normal_map_fade_dq_vert.h" #include "simple_vert.h" #include "simple_textured_frag.h" @@ -95,6 +99,7 @@ #include "model_shadow_vert.h" #include "skin_model_shadow_vert.h" +#include "skin_model_shadow_dq_vert.h" #include "model_shadow_frag.h" #include "skin_model_shadow_frag.h" @@ -195,16 +200,28 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelTranslucentVertex = model_translucent_vert::getShader(); auto modelTranslucentNormalMapVertex = model_translucent_normal_map_vert::getShader(); auto modelShadowVertex = model_shadow_vert::getShader(); + + auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader(); + auto modelLightmapNormalMapFadeVertex = model_lightmap_normal_map_fade_vert::getShader(); + + // matrix palette skinned auto skinModelVertex = skin_model_vert::getShader(); auto skinModelNormalMapVertex = skin_model_normal_map_vert::getShader(); auto skinModelShadowVertex = skin_model_shadow_vert::getShader(); - auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader(); - auto modelLightmapNormalMapFadeVertex = model_lightmap_normal_map_fade_vert::getShader(); auto skinModelFadeVertex = skin_model_fade_vert::getShader(); auto skinModelNormalMapFadeVertex = skin_model_normal_map_fade_vert::getShader(); auto skinModelTranslucentVertex = skinModelFadeVertex; // We use the same because it ouputs world position per vertex auto skinModelNormalMapTranslucentVertex = skinModelNormalMapFadeVertex; // We use the same because it ouputs world position per vertex + // dual quaternion skinned + auto skinModelDualQuatVertex = skin_model_dq_vert::getShader(); + auto skinModelNormalMapDualQuatVertex = skin_model_normal_map_dq_vert::getShader(); + auto skinModelShadowDualQuatVertex = skin_model_shadow_dq_vert::getShader(); + auto skinModelFadeDualQuatVertex = skin_model_fade_dq_vert::getShader(); + auto skinModelNormalMapFadeDualQuatVertex = skin_model_normal_map_fade_dq_vert::getShader(); + auto skinModelTranslucentDualQuatVertex = skinModelFadeDualQuatVertex; // We use the same because it ouputs world position per vertex + auto skinModelNormalMapTranslucentDualQuatVertex = skinModelNormalMapFadeDualQuatVertex; // We use the same because it ouputs world position per vertex + auto modelFadeVertex = model_fade_vert::getShader(); auto modelNormalMapFadeVertex = model_normal_map_fade_vert::getShader(); auto simpleFadeVertex = simple_fade_vert::getShader(); @@ -376,7 +393,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip Key::Builder().withMaterial().withLightmap().withTangents().withSpecular().withFade(), modelLightmapNormalMapFadeVertex, modelLightmapNormalSpecularMapFadePixel, batchSetter, itemSetter); - // Skinned + // matrix palette skinned addPipeline( Key::Builder().withMaterial().withSkinned(), skinModelVertex, modelPixel, nullptr, nullptr); @@ -403,7 +420,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip Key::Builder().withMaterial().withSkinned().withTangents().withSpecular().withFade(), skinModelNormalMapFadeVertex, modelNormalSpecularMapFadePixel, batchSetter, itemSetter); - // Skinned and Translucent + // matrix palette skinned and translucent addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent(), skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); @@ -430,6 +447,60 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withSpecular().withFade(), skinModelNormalMapFadeVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); + // dual quatenion skinned + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned(), + skinModelDualQuatVertex, modelPixel, nullptr, nullptr); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents(), + skinModelNormalMapDualQuatVertex, modelNormalMapPixel, nullptr, nullptr); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withSpecular(), + skinModelDualQuatVertex, modelSpecularMapPixel, nullptr, nullptr); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents().withSpecular(), + skinModelNormalMapDualQuatVertex, modelNormalSpecularMapPixel, nullptr, nullptr); + // Same thing but with Fade on + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withFade(), + skinModelFadeDualQuatVertex, modelFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents().withFade(), + skinModelNormalMapFadeDualQuatVertex, modelNormalMapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withSpecular().withFade(), + skinModelFadeDualQuatVertex, modelSpecularMapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTangents().withSpecular().withFade(), + skinModelNormalMapFadeDualQuatVertex, modelNormalSpecularMapFadePixel, batchSetter, itemSetter); + + // dual quaternion skinned and translucent + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent(), + skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents(), + skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withSpecular(), + skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withSpecular(), + skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); + // Same thing but with Fade on + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withFade(), + skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withFade(), + skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withSpecular().withFade(), + skinModelFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + addPipeline( + Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withSpecular().withFade(), + skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); + // Depth-only addPipeline( Key::Builder().withDepthOnly(), diff --git a/libraries/render-utils/src/Skinning.slh b/libraries/render-utils/src/Skinning.slh index 6048ba4ade..fbfe6b7185 100644 --- a/libraries/render-utils/src/Skinning.slh +++ b/libraries/render-utils/src/Skinning.slh @@ -11,18 +11,16 @@ <@if not SKINNING_SLH@> <@def SKINNING_SLH@> -// Use dual quaternion skinning -// Must match #define SKIN_DQ in Model.h -<@def SKIN_DQ@> - const int MAX_CLUSTERS = 128; const int INDICES_PER_VERTEX = 4; +<@func declareUseDualQuaternionSkinning(USE_DUAL_QUATERNION_SKINNING)@> + layout(std140) uniform skinClusterBuffer { mat4 clusterMatrices[MAX_CLUSTERS]; }; -<@if SKIN_DQ@> +<@if USE_DUAL_QUATERNION_SKINNING@> mat4 dualQuatToMat4(vec4 real, vec4 dual) { float twoRealXSq = 2.0 * real.x * real.x; @@ -211,7 +209,7 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v skinnedTangent = vec3(m * vec4(inTangent, 0)); } -<@else@> // SKIN_DQ +<@else@> // USE_DUAL_QUATERNION_SKINNING void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) { vec4 newPosition = vec4(0.0, 0.0, 0.0, 0.0); @@ -260,6 +258,8 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v skinnedTangent = newTangent.xyz; } -<@endif@> // if SKIN_DQ +<@endif@> // if USE_DUAL_QUATERNION_SKINNING + +<@endfunc@> // func declareUseDualQuaternionSkinning(USE_DUAL_QUATERNION_SKINNING) <@endif@> // if not SKINNING_SLH diff --git a/libraries/render-utils/src/skin_model.slv b/libraries/render-utils/src/skin_model.slv index 4236508edb..bd1655fc40 100644 --- a/libraries/render-utils/src/skin_model.slv +++ b/libraries/render-utils/src/skin_model.slv @@ -18,6 +18,7 @@ <$declareStandardTransform()$> <@include Skinning.slh@> +<$declareUseDualQuaternionSkinning()$> <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> diff --git a/libraries/render-utils/src/skin_model_dq.slv b/libraries/render-utils/src/skin_model_dq.slv new file mode 100644 index 0000000000..96f9b4a713 --- /dev/null +++ b/libraries/render-utils/src/skin_model_dq.slv @@ -0,0 +1,52 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model.vert +// vertex shader +// +// Created by Andrzej Kapolka on 10/14/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Skinning.slh@> +<$declareUseDualQuaternionSkinning(1)$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out vec4 _position; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec3 _normal; +out vec3 _color; +out float _alpha; + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + vec3 interpolatedNormal = vec3(0.0, 0.0, 0.0); + + skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); + + // pass along the color + _color = colorToLinearRGB(inColor.rgb); + _alpha = inColor.a; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normal.xyz)$> +} diff --git a/libraries/render-utils/src/skin_model_fade.slv b/libraries/render-utils/src/skin_model_fade.slv index fa8e1f8991..b14bf1532e 100644 --- a/libraries/render-utils/src/skin_model_fade.slv +++ b/libraries/render-utils/src/skin_model_fade.slv @@ -18,6 +18,7 @@ <$declareStandardTransform()$> <@include Skinning.slh@> +<$declareUseDualQuaternionSkinning()$> <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> diff --git a/libraries/render-utils/src/skin_model_fade_dq.slv b/libraries/render-utils/src/skin_model_fade_dq.slv new file mode 100644 index 0000000000..4f8a923a03 --- /dev/null +++ b/libraries/render-utils/src/skin_model_fade_dq.slv @@ -0,0 +1,54 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model_fade.vert +// vertex shader +// +// Created by Olivier Prat on 06/045/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Skinning.slh@> +<$declareUseDualQuaternionSkinning(1)$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out vec4 _position; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec3 _normal; +out vec3 _color; +out float _alpha; +out vec4 _worldPosition; + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + vec3 interpolatedNormal = vec3(0.0, 0.0, 0.0); + + skinPositionNormal(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, position, interpolatedNormal); + + // pass along the color + _color = colorToLinearRGB(inColor.rgb); + _alpha = inColor.a; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> + <$transformModelToWorldPos(obj, position, _worldPosition)$> + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, _normal.xyz)$> +} diff --git a/libraries/render-utils/src/skin_model_normal_map.slv b/libraries/render-utils/src/skin_model_normal_map.slv index 9f1087f87a..666bdf865f 100644 --- a/libraries/render-utils/src/skin_model_normal_map.slv +++ b/libraries/render-utils/src/skin_model_normal_map.slv @@ -18,6 +18,7 @@ <$declareStandardTransform()$> <@include Skinning.slh@> +<$declareUseDualQuaternionSkinning()$> <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> diff --git a/libraries/render-utils/src/skin_model_normal_map_dq.slv b/libraries/render-utils/src/skin_model_normal_map_dq.slv new file mode 100644 index 0000000000..02b3742f6f --- /dev/null +++ b/libraries/render-utils/src/skin_model_normal_map_dq.slv @@ -0,0 +1,61 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model_normal_map.vert +// vertex shader +// +// Created by Andrzej Kapolka on 10/29/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Skinning.slh@> +<$declareUseDualQuaternionSkinning(1)$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out vec4 _position; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec3 _normal; +out vec3 _tangent; +out vec3 _color; +out float _alpha; + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + vec4 interpolatedNormal = vec4(0.0, 0.0, 0.0, 0.0); + vec4 interpolatedTangent = vec4(0.0, 0.0, 0.0, 0.0); + + skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); + + // pass along the color + _color = colorToLinearRGB(inColor.rgb); + _alpha = inColor.a; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); + interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> + <$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> + + _normal = interpolatedNormal.xyz; + _tangent = interpolatedTangent.xyz; +} diff --git a/libraries/render-utils/src/skin_model_normal_map_fade.slv b/libraries/render-utils/src/skin_model_normal_map_fade.slv index 4e638866fc..d72e47702d 100644 --- a/libraries/render-utils/src/skin_model_normal_map_fade.slv +++ b/libraries/render-utils/src/skin_model_normal_map_fade.slv @@ -18,6 +18,7 @@ <$declareStandardTransform()$> <@include Skinning.slh@> +<$declareUseDualQuaternionSkinning()$> <@include MaterialTextures.slh@> <$declareMaterialTexMapArrayBuffer()$> diff --git a/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv new file mode 100644 index 0000000000..02b3742f6f --- /dev/null +++ b/libraries/render-utils/src/skin_model_normal_map_fade_dq.slv @@ -0,0 +1,61 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model_normal_map.vert +// vertex shader +// +// Created by Andrzej Kapolka on 10/29/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> +<@include gpu/Color.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Skinning.slh@> +<$declareUseDualQuaternionSkinning(1)$> + +<@include MaterialTextures.slh@> +<$declareMaterialTexMapArrayBuffer()$> + +out vec4 _position; +out vec2 _texCoord0; +out vec2 _texCoord1; +out vec3 _normal; +out vec3 _tangent; +out vec3 _color; +out float _alpha; + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + vec4 interpolatedNormal = vec4(0.0, 0.0, 0.0, 0.0); + vec4 interpolatedTangent = vec4(0.0, 0.0, 0.0, 0.0); + + skinPositionNormalTangent(inSkinClusterIndex, inSkinClusterWeight, inPosition, inNormal.xyz, inTangent.xyz, position, interpolatedNormal.xyz, interpolatedTangent.xyz); + + // pass along the color + _color = colorToLinearRGB(inColor.rgb); + _alpha = inColor.a; + + TexMapArray texMapArray = getTexMapArray(); + <$evalTexMapArrayTexcoord0(texMapArray, inTexCoord0, _texCoord0)$> + <$evalTexMapArrayTexcoord1(texMapArray, inTexCoord0, _texCoord1)$> + + interpolatedNormal = vec4(normalize(interpolatedNormal.xyz), 0.0); + interpolatedTangent = vec4(normalize(interpolatedTangent.xyz), 0.0); + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToEyeAndClipPos(cam, obj, position, _position, gl_Position)$> + <$transformModelToWorldDir(cam, obj, interpolatedNormal.xyz, interpolatedNormal.xyz)$> + <$transformModelToWorldDir(cam, obj, interpolatedTangent.xyz, interpolatedTangent.xyz)$> + + _normal = interpolatedNormal.xyz; + _tangent = interpolatedTangent.xyz; +} diff --git a/libraries/render-utils/src/skin_model_shadow.slv b/libraries/render-utils/src/skin_model_shadow.slv index 6684cfea80..03da2e074e 100644 --- a/libraries/render-utils/src/skin_model_shadow.slv +++ b/libraries/render-utils/src/skin_model_shadow.slv @@ -17,6 +17,7 @@ <$declareStandardTransform()$> <@include Skinning.slh@> +<$declareUseDualQuaternionSkinning()$> void main(void) { vec4 position = vec4(0.0, 0.0, 0.0, 0.0); diff --git a/libraries/render-utils/src/skin_model_shadow_dq.slv b/libraries/render-utils/src/skin_model_shadow_dq.slv new file mode 100644 index 0000000000..74cd4076bc --- /dev/null +++ b/libraries/render-utils/src/skin_model_shadow_dq.slv @@ -0,0 +1,30 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model_shadow.vert +// vertex shader +// +// Created by Andrzej Kapolka on 3/24/14. +// Copyright 2014 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 gpu/Inputs.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Skinning.slh@> +<$declareUseDualQuaternionSkinning(1)$> + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + skinPosition(inSkinClusterIndex, inSkinClusterWeight, inPosition, position); + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, position, gl_Position)$> +} diff --git a/libraries/render-utils/src/skin_model_shadow_fade.slv b/libraries/render-utils/src/skin_model_shadow_fade.slv index 7b27263569..d2e79f9d74 100644 --- a/libraries/render-utils/src/skin_model_shadow_fade.slv +++ b/libraries/render-utils/src/skin_model_shadow_fade.slv @@ -17,6 +17,7 @@ <$declareStandardTransform()$> <@include Skinning.slh@> +<$declareUseDualQuaternionSkinning()$> out vec4 _worldPosition; diff --git a/libraries/render-utils/src/skin_model_shadow_fade_dq.slv b/libraries/render-utils/src/skin_model_shadow_fade_dq.slv new file mode 100644 index 0000000000..fb9c60eefd --- /dev/null +++ b/libraries/render-utils/src/skin_model_shadow_fade_dq.slv @@ -0,0 +1,33 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// skin_model_shadow_fade.vert +// vertex shader +// +// Created by Olivier Prat on 06/045/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include Skinning.slh@> +<$declareUseDualQuaternionSkinning(1)$> + +out vec4 _worldPosition; + +void main(void) { + vec4 position = vec4(0.0, 0.0, 0.0, 0.0); + skinPosition(inSkinClusterIndex, inSkinClusterWeight, inPosition, position); + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, position, gl_Position)$> + <$transformModelToWorldPos(obj, position, _worldPosition)$> +} diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index 1dd9f5da49..f175bab99a 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -32,6 +32,7 @@ public: SPECULAR, UNLIT, SKINNED, + DUAL_QUAT_SKINNED, DEPTH_ONLY, DEPTH_BIAS, WIREFRAME, @@ -80,6 +81,7 @@ public: Builder& withSpecular() { _flags.set(SPECULAR); return (*this); } Builder& withUnlit() { _flags.set(UNLIT); return (*this); } Builder& withSkinned() { _flags.set(SKINNED); return (*this); } + Builder& withDualQuatSkinned() { _flags.set(DUAL_QUAT_SKINNED); return (*this); } Builder& withDepthOnly() { _flags.set(DEPTH_ONLY); return (*this); } Builder& withDepthBias() { _flags.set(DEPTH_BIAS); return (*this); } Builder& withWireframe() { _flags.set(WIREFRAME); return (*this); } @@ -133,6 +135,9 @@ public: Builder& withSkinned() { _flags.set(SKINNED); _mask.set(SKINNED); return (*this); } Builder& withoutSkinned() { _flags.reset(SKINNED); _mask.set(SKINNED); return (*this); } + Builder& withDualQuatSkinned() { _flags.set(DUAL_QUAT_SKINNED); _mask.set(SKINNED); return (*this); } + Builder& withoutDualQuatSkinned() { _flags.reset(DUAL_QUAT_SKINNED); _mask.set(SKINNED); return (*this); } + Builder& withDepthOnly() { _flags.set(DEPTH_ONLY); _mask.set(DEPTH_ONLY); return (*this); } Builder& withoutDepthOnly() { _flags.reset(DEPTH_ONLY); _mask.set(DEPTH_ONLY); return (*this); } From 1632ab9782cd4ed566ca3b9e417994ec657e6a7a Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 13 Feb 2018 18:27:36 -0800 Subject: [PATCH 20/80] Changed flag to "cast shadow" instead of "don't cast shadow". --- libraries/entities/src/EntityItemProperties.h | 2 +- libraries/entities/src/EntityItemPropertiesDefaults.h | 2 +- libraries/entities/src/EntityPropertyFlags.h | 2 +- libraries/entities/src/ModelEntityItem.cpp | 6 +++--- libraries/entities/src/ModelEntityItem.h | 2 +- libraries/entities/src/ShapeEntityItem.cpp | 6 +++--- libraries/entities/src/ShapeEntityItem.h | 2 +- libraries/render-utils/src/RenderShadowTask.cpp | 4 ++-- libraries/render/src/render/Item.h | 4 ---- 9 files changed, 13 insertions(+), 17 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 731bb9390e..8feebd3979 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -128,7 +128,7 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); - DEFINE_PROPERTY(PROP_DONT_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_DONT_CAST_SHADOW); + DEFINE_PROPERTY(PROP_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAST_SHADOW); DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor, particle::DEFAULT_COLOR); DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor, particle::DEFAULT_COLOR_SPREAD); DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, xColor, particle::DEFAULT_COLOR); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 181ba9bbf2..f85d55dc3a 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -46,7 +46,7 @@ const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; -const bool ENTITY_ITEM_DEFAULT_DONT_CAST_SHADOW { false }; +const bool ENTITY_ITEM_DEFAULT_CAST_SHADOW { true }; const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); const quint64 ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP = 0; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index fbc4199097..d2de67735c 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -31,7 +31,7 @@ enum EntityPropertyList { PROP_SCRIPT, // these properties are supported by some derived classes - PROP_DONT_CAST_SHADOW, + PROP_CAST_SHADOW, PROP_COLOR, // these are used by models only diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 73cf29bce1..6d15fded31 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -116,7 +116,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, const unsigned char* dataAt = data; bool animationPropertiesChanged = false; - READ_ENTITY_PROPERTY(PROP_DONT_CAST_SHADOW, bool, setCanCastShadow); + READ_ENTITY_PROPERTY(PROP_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); @@ -153,7 +153,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_DONT_CAST_SHADOW; + requestedProperties += PROP_CAST_SHADOW; requestedProperties += PROP_MODEL_URL; requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_TEXTURES; @@ -178,7 +178,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_DONT_CAST_SHADOW, getCanCastShadow()); + APPEND_ENTITY_PROPERTY(PROP_CAST_SHADOW, getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, getModelURL()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index ec65876d84..49f20c48e2 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -174,7 +174,7 @@ protected: ShapeType _shapeType = SHAPE_TYPE_NONE; - bool _canCastShadow{ ENTITY_ITEM_DEFAULT_DONT_CAST_SHADOW }; + bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAST_SHADOW }; private: uint64_t _lastAnimated{ 0 }; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 2fbb6702f2..22c3bab4f3 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -157,7 +157,7 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape); READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); - READ_ENTITY_PROPERTY(PROP_DONT_CAST_SHADOW, bool, setCanCastShadow); + READ_ENTITY_PROPERTY(PROP_CAST_SHADOW, bool, setCanCastShadow); return bytesRead; } @@ -169,7 +169,7 @@ EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_SHAPE; requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; - requestedProperties += PROP_DONT_CAST_SHADOW; + requestedProperties += PROP_CAST_SHADOW; return requestedProperties; } @@ -186,7 +186,7 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); - APPEND_ENTITY_PROPERTY(PROP_DONT_CAST_SHADOW, getCanCastShadow()); + APPEND_ENTITY_PROPERTY(PROP_CAST_SHADOW, getCanCastShadow()); } void ShapeEntityItem::setColor(const rgbColor& value) { diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 63c4eb58c5..4a08936bce 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -115,7 +115,7 @@ protected: //! ellipsoids. ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; - bool _canCastShadow { ENTITY_ITEM_DEFAULT_DONT_CAST_SHADOW }; + bool _canCastShadow { ENTITY_ITEM_DEFAULT_CAST_SHADOW }; }; #endif // hifi_ShapeEntityItem_h diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index eef641e369..e34f550def 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -224,7 +224,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto setupOutput = task.addJob("ShadowSetup"); const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene - static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withNoShadowCaster(); + static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withShadowCaster(); const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); @@ -398,7 +398,7 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon const auto globalShadow = lightStage->getCurrentKeyShadow(); if (globalShadow && _cascadeIndexgetCascadeCount()) { - output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withNoShadowCaster(); + output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withShadowCaster(); // Set the keylight render args auto& cascade = globalShadow->getCascade(_cascadeIndex); diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 7713c1ea7e..ff4b3a0458 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -120,10 +120,7 @@ public: Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); } Builder& withDeformed() { _flags.set(DEFORMED); return (*this); } Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); } - - Builder& withNoShadowCaster() { _flags.reset(SHADOW_CASTER); return (*this); } Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } - Builder& withLayered() { _flags.set(LAYERED); return (*this); } Builder& withTag(Tag tag) { _flags.set(FIRST_TAG_BIT + tag); return (*this); } @@ -158,7 +155,6 @@ public: bool isInvisible() const { return _flags[INVISIBLE]; } bool isShadowCaster() const { return _flags[SHADOW_CASTER]; } - bool isNotShadowCaster() const { return !_flags[SHADOW_CASTER]; } bool isLayered() const { return _flags[LAYERED]; } bool isSpatial() const { return !isLayered(); } From a92765a83a3578572c1ce9951c807d417b6744f5 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 14 Feb 2018 12:01:30 -0800 Subject: [PATCH 21/80] Adding can cast shadow property. --- .../entities/src/EntityItemProperties.cpp | 9 +++++ libraries/entities/src/EntityItemProperties.h | 2 +- libraries/entities/src/EntityPropertyFlags.h | 2 +- libraries/entities/src/ModelEntityItem.cpp | 8 ++-- libraries/entities/src/ShapeEntityItem.cpp | 6 +-- scripts/system/html/entityProperties.html | 38 +++++++++---------- scripts/system/html/js/entityProperties.js | 29 ++++++++++++-- 7 files changed, 62 insertions(+), 32 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index cca4e858fa..79c36180d6 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -290,6 +290,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL); CHECK_PROPERTY_CHANGE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible); + CHECK_PROPERTY_CHANGE(PROP_CAN_CAST_SHADOW, canCastShadow); CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint); CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity); CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping); @@ -625,6 +626,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); } + // Models and Shapes + if (_type == EntityTypes::Model || _type == EntityTypes::Shape || _type == EntityTypes::Box || _type == EntityTypes::Sphere) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); + } + if (!skipDefaults && !strictSemantics) { AABox aaBox = getAABox(); QScriptValue boundingBox = engine->newObject(); @@ -707,6 +713,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(angularVelocity, glmVec3, setAngularVelocity); COPY_PROPERTY_FROM_QSCRIPTVALUE(angularDamping, float, setAngularDamping); COPY_PROPERTY_FROM_QSCRIPTVALUE(visible, bool, setVisible); + COPY_PROPERTY_FROM_QSCRIPTVALUE(canCastShadow, bool, setCanCastShadow); COPY_PROPERTY_FROM_QSCRIPTVALUE(color, xColor, setColor); COPY_PROPERTY_FROM_QSCRIPTVALUE(colorSpread, xColor, setColorSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(colorStart, xColor, setColorStart); @@ -1851,6 +1858,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int properties.getType() == EntityTypes::Box || properties.getType() == EntityTypes::Sphere) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); } READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); @@ -1974,6 +1982,7 @@ void EntityItemProperties::markAllChanged() { _angularDampingChanged = true; _nameChanged = true; _visibleChanged = true; + _canCastShadowChanged = true; _colorChanged = true; _alphaChanged = true; _modelURLChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 8feebd3979..ec10910092 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -128,7 +128,7 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); - DEFINE_PROPERTY(PROP_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAST_SHADOW); + DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAST_SHADOW); DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor, particle::DEFAULT_COLOR); DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor, particle::DEFAULT_COLOR_SPREAD); DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, xColor, particle::DEFAULT_COLOR); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index d2de67735c..ab17f2a873 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -31,7 +31,7 @@ enum EntityPropertyList { PROP_SCRIPT, // these properties are supported by some derived classes - PROP_CAST_SHADOW, + PROP_CAN_CAST_SHADOW, PROP_COLOR, // these are used by models only diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 6d15fded31..7c14d8a4a0 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -116,7 +116,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, const unsigned char* dataAt = data; bool animationPropertiesChanged = false; - READ_ENTITY_PROPERTY(PROP_CAST_SHADOW, bool, setCanCastShadow); + READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); @@ -153,7 +153,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_CAST_SHADOW; + requestedProperties += PROP_CAN_CAST_SHADOW; requestedProperties += PROP_MODEL_URL; requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_TEXTURES; @@ -178,7 +178,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_CAST_SHADOW, getCanCastShadow()); + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, getModelURL()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); @@ -619,6 +619,7 @@ void ModelEntityItem::setColor(const rgbColor& value) { }); } +#pragma optimize("", off) void ModelEntityItem::setColor(const xColor& value) { withWriteLock([&] { _color[RED_INDEX] = value.red; @@ -666,7 +667,6 @@ bool ModelEntityItem::getAnimationLoop() const { }); } - void ModelEntityItem::setAnimationHold(bool hold) { withWriteLock([&] { _animationProperties.setHold(hold); diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 22c3bab4f3..9d81e850df 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -157,7 +157,7 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape); READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); - READ_ENTITY_PROPERTY(PROP_CAST_SHADOW, bool, setCanCastShadow); + READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); return bytesRead; } @@ -169,7 +169,7 @@ EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_SHAPE; requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; - requestedProperties += PROP_CAST_SHADOW; + requestedProperties += PROP_CAN_CAST_SHADOW; return requestedProperties; } @@ -186,7 +186,7 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); - APPEND_ENTITY_PROPERTY(PROP_CAST_SHADOW, getCanCastShadow()); + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); } void ShapeEntityItem::setColor(const rgbColor& value) { diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 856ca3c6e1..1420e85b11 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -42,25 +42,28 @@
-
+
+ +
@@ -293,7 +296,6 @@
-
BehaviorM @@ -365,8 +367,6 @@
- -
LightM @@ -400,7 +400,6 @@
-
ModelM @@ -484,7 +483,6 @@
-
ZoneM diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 7e8827a9b5..846a1da21a 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -595,6 +595,8 @@ function loaded() { var elShape = document.getElementById("property-shape"); + var elCanCastShadow = document.getElementById("property-can-cast-shadow"); + var elLightSpotLight = document.getElementById("property-light-spot-light"); var elLightColor = document.getElementById("property-light-color"); var elLightColorRed = document.getElementById("property-light-color-red"); @@ -799,7 +801,6 @@ function loaded() { elLocked.checked = properties.locked; - elName.value = properties.name; elVisible.checked = properties.visible; @@ -966,6 +967,12 @@ function loaded() { properties.color.green + "," + properties.color.blue + ")"; } + if (properties.type === "Model" || + properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { + + elCanCastShadow = properties.canCastShadow; + } + if (properties.type === "Model") { elModelURL.value = properties.modelURL; elShapeType.value = properties.shapeType; @@ -1012,7 +1019,6 @@ function loaded() { elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); elLightExponent.value = properties.exponent.toFixed(2); elLightCutoff.value = properties.cutoff.toFixed(2); - } else if (properties.type === "Zone") { // Key light elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); @@ -1093,13 +1099,15 @@ function loaded() { // Show/hide sections as required showElements(document.getElementsByClassName('skybox-section'), elZoneSkyboxModeEnabled.checked); + showElements(document.getElementsByClassName('keylight-section'), elZoneKeyLightModeEnabled.checked); + showElements(document.getElementsByClassName('ambient-section'), elZoneAmbientLightModeEnabled.checked); + showElements(document.getElementsByClassName('haze-section'), elZoneHazeModeEnabled.checked); - } else if (properties.type === "PolyVox") { elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); @@ -1111,6 +1119,15 @@ function loaded() { elZTextureURL.value = properties.zTextureURL; } + // Only these types can cast a shadow + if (properties.type === "Model" || + properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { + + showElements(document.getElementsByClassName('can-cast-shadow-section'), true); + } else { + showElements(document.getElementsByClassName('can-cast-shadow-section'), false); + } + if (properties.locked) { disableProperties(); elLocked.removeAttribute('disabled'); @@ -1356,6 +1373,12 @@ function loaded() { elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); + if (properties.type === "Model" || + properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { + + elCanCastShadow.addEventListener('change', createEmitTextPropertyUpdateFunction('canCastShadow')); + } + elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi', 0)); From 611c67bf2f6914a7df7edc49be24477ded182572 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Thu, 15 Feb 2018 02:00:51 +0300 Subject: [PATCH 22/80] FB12297 - HMD: Disabled preview mode only occurs during step 2 of the wallet setup wizard --- interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index fab27a29bb..bad592067c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -441,7 +441,7 @@ Item { } Item { id: choosePassphraseContainer; - visible: root.activeView === "step_3"; + visible: root.hasShownSecurityImageTip && root.activeView === "step_3"; // Anchors anchors.top: titleBarContainer.bottom; anchors.topMargin: 30; @@ -451,7 +451,10 @@ Item { onVisibleChanged: { if (visible) { + sendSignalToWallet({method: 'disableHmdPreview'}); Commerce.getWalletAuthenticatedStatus(); + } else { + sendSignalToWallet({method: 'maybeEnableHmdPreview'}); } } From a679b6f82841b701c06c313b57d18de1c3d1c672 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 14 Feb 2018 18:55:04 -0800 Subject: [PATCH 23/80] Fixed possible crash. --- libraries/render-utils/src/DeferredLightingEffect.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 2e77d702c6..665e767c7c 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -550,7 +550,9 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, if (lightStage && lightStage->_currentFrame._sunLights.size()) { graphics::LightPointer keyLight = lightStage->getLight(lightStage->_currentFrame._sunLights.front()); - keyLightCastShadows = keyLight->getCastShadows(); + if (keyLight) { + keyLightCastShadows = keyLight->getCastShadows(); + } } if (deferredLightingEffect->_shadowMapEnabled && keyLightCastShadows) { From ef771b6db49ff48655fe772d01b9f6683e1e3234 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 14 Feb 2018 19:11:13 -0800 Subject: [PATCH 24/80] Minor indentation correction. --- scripts/system/html/entityProperties.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 1cfa5c3899..7dbeae067c 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -678,7 +678,7 @@ min="-1000" max="50000" step="10"> -
+
From 12f4b8dbb19321c74c7b967b569627b87b8c23aa Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 14 Feb 2018 19:12:24 -0800 Subject: [PATCH 25/80] Corrected event listener for canCastShadow - still bad. --- scripts/system/html/js/entityProperties.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 9502e9f4d4..8868159848 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1392,12 +1392,8 @@ function loaded() { elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - - elCanCastShadow.addEventListener('change', createEmitTextPropertyUpdateFunction('canCastShadow')); - } - + elCanCastShadow.addEventListener('change', createEmitCheckedPropertyUpdateFunction('canCastShadow')); + elImageURL.addEventListener('change', createImageURLUpdateFunction('textures')); elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); From 26e7a85a955db374b526f8a117585ff6c47ccbd2 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 14 Feb 2018 21:15:29 -0800 Subject: [PATCH 26/80] Fixed possible crash. --- libraries/render-utils/src/RenderShadowTask.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index e34f550def..24a14a697c 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -386,6 +386,10 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon assert(lightStage); // Exit if current keylight does not cast shadows + if (!lightStage->getCurrentKeyLight()) { + return; + } + bool castShadows = lightStage->getCurrentKeyLight()->getCastShadows(); if (!castShadows) { output.edit0() = ItemFilter::Builder::nothing(); From adb02d69f9c50721d9e186870ae8ca6cf201a413 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 14 Feb 2018 21:45:56 -0800 Subject: [PATCH 27/80] WIP -adding canCastShadow flag. --- libraries/entities/src/EntityItemProperties.cpp | 5 +++++ libraries/entities/src/EntityItemProperties.h | 3 ++- .../entities/src/EntityItemPropertiesDefaults.h | 3 ++- libraries/entities/src/ModelEntityItem.h | 2 +- libraries/entities/src/ShapeEntityItem.h | 2 +- scripts/system/html/js/entityProperties.js | 16 ++++++++-------- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 79c36180d6..3c3c0742da 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -878,6 +878,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(angularVelocity); COPY_PROPERTY_IF_CHANGED(angularDamping); COPY_PROPERTY_IF_CHANGED(visible); + COPY_PROPERTY_IF_CHANGED(canCastShadow); COPY_PROPERTY_IF_CHANGED(color); COPY_PROPERTY_IF_CHANGED(colorSpread); COPY_PROPERTY_IF_CHANGED(colorStart); @@ -1050,6 +1051,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue std::call_once(initMap, [](){ ADD_PROPERTY_TO_MAP(PROP_VISIBLE, Visible, visible, bool); + ADD_PROPERTY_TO_MAP(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool); ADD_PROPERTY_TO_MAP(PROP_POSITION, Position, position, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_ROTATION, Rotation, rotation, glm::quat); @@ -2172,6 +2174,9 @@ QList EntityItemProperties::listChangedProperties() { if (visibleChanged()) { out += "visible"; } + if (canCastShadowChanged()) { + out += "canCastShadow"; + } if (rotationChanged()) { out += "rotation"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index ec10910092..dcec1a1f81 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -128,7 +128,7 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); - DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAST_SHADOW); + DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor, particle::DEFAULT_COLOR); DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor, particle::DEFAULT_COLOR_SPREAD); DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, xColor, particle::DEFAULT_COLOR); @@ -415,6 +415,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Velocity, velocity, "in meters"); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Name, name, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Visible, visible, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, CanCastShadow, canCastShadow, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Rotation, rotation, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Density, density, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Gravity, gravity, ""); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index f85d55dc3a..efbf45ce8d 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -46,7 +46,8 @@ const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; -const bool ENTITY_ITEM_DEFAULT_CAST_SHADOW { true }; +const bool ENTITY_ITEM_DEFAULT_CAST_SHADOWS { true }; +const bool ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW { false }; const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); const quint64 ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP = 0; diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 49f20c48e2..791eebb7d9 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -174,7 +174,7 @@ protected: ShapeType _shapeType = SHAPE_TYPE_NONE; - bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAST_SHADOW }; + bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; private: uint64_t _lastAnimated{ 0 }; diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 4a08936bce..4bc008f761 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -115,7 +115,7 @@ protected: //! ellipsoids. ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; - bool _canCastShadow { ENTITY_ITEM_DEFAULT_CAST_SHADOW }; + bool _canCastShadow { ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; }; #endif // hifi_ShapeEntityItem_h diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 8868159848..fca43c4665 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -802,11 +802,11 @@ function loaded() { // HTML workaround since image is not yet a separate entity type var IMAGE_MODEL_NAME = 'default-image-model.fbx'; - var urlParts = properties.modelURL.split('/') - var propsFilename = urlParts[urlParts.length - 1]; - if (properties.type === "Model" && propsFilename === IMAGE_MODEL_NAME) { - properties.type = "Image"; - } +//// var urlParts = properties.modelURL.split('/') +//// var propsFilename = urlParts[urlParts.length - 1]; +//// if (properties.type === "Model" && propsFilename === IMAGE_MODEL_NAME) { +//// properties.type = "Image"; +//// } // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; @@ -983,11 +983,11 @@ function loaded() { properties.color.green + "," + properties.color.blue + ")"; } - if (properties.type === "Model" || - properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { + //if (properties.type === "Model" || + // properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { elCanCastShadow = properties.canCastShadow; - } + //} if (properties.type === "Model") { elModelURL.value = properties.modelURL; From 7e99570824418cc1a57b72cad44ddd8c386a1bff Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 15 Feb 2018 07:54:55 -0800 Subject: [PATCH 28/80] Fixed Ubuntu warnings. --- libraries/entities/src/ModelEntityItem.cpp | 1 - libraries/graphics/src/graphics/Light.cpp | 2 +- libraries/graphics/src/graphics/Light.h | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 7c14d8a4a0..b1edd47a67 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -619,7 +619,6 @@ void ModelEntityItem::setColor(const rgbColor& value) { }); } -#pragma optimize("", off) void ModelEntityItem::setColor(const xColor& value) { withWriteLock([&] { _color[RED_INDEX] = value.red; diff --git a/libraries/graphics/src/graphics/Light.cpp b/libraries/graphics/src/graphics/Light.cpp index 50601299dd..76d8a6030a 100755 --- a/libraries/graphics/src/graphics/Light.cpp +++ b/libraries/graphics/src/graphics/Light.cpp @@ -69,7 +69,7 @@ void Light::setCastShadows(const bool castShadows) { _castShadows = castShadows; } -const bool Light::getCastShadows() const { +bool Light::getCastShadows() const { return _castShadows; } diff --git a/libraries/graphics/src/graphics/Light.h b/libraries/graphics/src/graphics/Light.h index ebe22d5593..bb9fb3e5b9 100755 --- a/libraries/graphics/src/graphics/Light.h +++ b/libraries/graphics/src/graphics/Light.h @@ -104,7 +104,7 @@ public: const Vec3& getDirection() const; void setCastShadows(const bool castShadows); - const bool getCastShadows() const; + bool getCastShadows() const; void setOrientation(const Quat& orientation); const glm::quat& getOrientation() const { return _transform.getRotation(); } From 777862f253c32afcfa1bd0988a986da0d5b7c7da Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 15 Feb 2018 11:50:38 -0800 Subject: [PATCH 29/80] can cast shadow flag now works correctly (UI/JS aspects). --- libraries/entities/src/EntityItemProperties.cpp | 9 +++++++++ libraries/entities/src/EntityItemProperties.h | 2 +- scripts/system/html/js/entityProperties.js | 8 ++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3c3c0742da..86404c6504 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1506,6 +1506,15 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } + // Only models and shapes (including cubes and spheres) can cast shadows + if (properties.getType() == EntityTypes::Model || + properties.getType() == EntityTypes::Shape || + properties.getType() == EntityTypes::Box || + properties.getType() == EntityTypes::Sphere) { + + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); + } + APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index dcec1a1f81..349d32f806 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -113,6 +113,7 @@ public: // bool _fooChanged { false }; DEFINE_PROPERTY(PROP_VISIBLE, Visible, visible, bool, ENTITY_ITEM_DEFAULT_VISIBLE); + DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3, ENTITY_ITEM_DEFAULT_DIMENSIONS); DEFINE_PROPERTY_REF(PROP_ROTATION, Rotation, rotation, glm::quat, ENTITY_ITEM_DEFAULT_ROTATION); @@ -128,7 +129,6 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); - DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor, particle::DEFAULT_COLOR); DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor, particle::DEFAULT_COLOR_SPREAD); DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, xColor, particle::DEFAULT_COLOR); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index fca43c4665..1b4df84f40 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -983,11 +983,11 @@ function loaded() { properties.color.green + "," + properties.color.blue + ")"; } - //if (properties.type === "Model" || - // properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { + if (properties.type === "Model" || + properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { - elCanCastShadow = properties.canCastShadow; - //} + elCanCastShadow.checked = properties.canCastShadow; + } if (properties.type === "Model") { elModelURL.value = properties.modelURL; From df7a8389b3102c9c4dc4b0bb74168f97a16343ab Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 15 Feb 2018 11:51:06 -0800 Subject: [PATCH 30/80] Fixed possible crash. --- libraries/render-utils/src/RenderShadowTask.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 24a14a697c..7806c95330 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -121,8 +121,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con assert(lightStage); // Exit if current keylight does not cast shadows - bool castShadows = lightStage->getCurrentKeyLight()->getCastShadows(); - if (!castShadows) { + if (!lightStage->getCurrentKeyLight() || !lightStage->getCurrentKeyLight()->getCastShadows()) { return; } From cee0bbf8a5914be7e254af8911bc22caedf82583 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 15 Feb 2018 11:32:29 -0800 Subject: [PATCH 31/80] Revert "FBX node IDs aren't alphanumerically ordered per logical structure" This reverts commit a7ec4501e65396d4c7bf2316dd73188cea7a3c7d. Because remainingModels is a QSet, the order is not guaranteed. Therefore the same code iterating over the same items will sometimes have a different ordering. See docs for QSet, http://doc.qt.io/qt-5/qset.html This was bug was causing scrambled avatars, because both the transmitter and receiver of the AvatarData packets make the strong assumption that the joint orders are same. When they are not the avatar's appear scrambled. (cherry picked from commit f07b1fa4c52af97f9adab2ba6e9678a75fe6aa2b) --- libraries/fbx/src/FBXReader.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 4ed1ca38dc..14f12b5d1b 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1481,6 +1481,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } while (!remainingModels.isEmpty()) { QString first = *remainingModels.constBegin(); + foreach (const QString& id, remainingModels) { + if (id < first) { + first = id; + } + } QString topID = getTopModelID(_connectionParentMap, models, first, url); appendModelIDs(_connectionParentMap.value(topID), _connectionChildMap, models, remainingModels, modelIDs, true); } From 72e89851f3c2936bbf3da1f065fb3bc4fb4b919e Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 15 Feb 2018 14:16:56 -0800 Subject: [PATCH 32/80] Added READ_ENTITY_PROPERTY_TO_PROPERTIES --- libraries/entities/src/EntityItemProperties.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 86404c6504..78c6c6a6b3 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1869,6 +1869,14 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int properties.getType() == EntityTypes::Box || properties.getType() == EntityTypes::Sphere) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); + } + + // Can cast shadow flag + if (properties.getType() == EntityTypes::Model || + properties.getType() == EntityTypes::Shape || + properties.getType() == EntityTypes::Box || + properties.getType() == EntityTypes::Sphere) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); } From 52f576e6f6449d6f0b16d8b9e1e4753cbc38765a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 15 Feb 2018 15:02:45 -0800 Subject: [PATCH 33/80] don't update avatar entities if the avatars ID is AVATAR_SELF_ID --- libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 86635cd3bf..1b1511e2c6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -219,7 +219,7 @@ void Avatar::updateAvatarEntities() { return; } - if (getID() == QUuid()) { + if (getID() == QUuid() || getID() == AVATAR_SELF_ID) { return; // wait until MyAvatar gets an ID before doing this. } From 194c7f41015575a91b4aa64f1775cc533689b5ec Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 16 Feb 2018 09:28:15 -0800 Subject: [PATCH 34/80] WIP - for review. --- interface/src/avatar/MyAvatar.cpp | 8 +-- interface/src/avatar/MyAvatar.h | 2 - .../src/RenderableEntityItem.h | 1 + .../src/RenderableModelEntityItem.cpp | 4 ++ .../render-utils/src/CauterizedModel.cpp | 5 +- .../render-utils/src/MeshPartPayload.cpp | 10 +++- libraries/render-utils/src/MeshPartPayload.h | 4 +- libraries/render-utils/src/Model.cpp | 55 ++++++++++++++----- libraries/render-utils/src/Model.h | 6 ++ 9 files changed, 67 insertions(+), 28 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c25aaeeecd..97b8c71a32 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1112,6 +1112,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); + _skeletonModel->setCanCastShadow(true, qApp->getMain3DScene()); } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { @@ -1464,6 +1465,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { int skeletonModelChangeCount = _skeletonModelChangeCount; Avatar::setSkeletonModelURL(skeletonModelURL); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); + _skeletonModel->setCanCastShadow(true, qApp->getMain3DScene()); _headBoneSet.clear(); _cauterizationNeedsUpdate = true; @@ -1819,12 +1821,6 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName, Avatar::attach(modelURL, jointName, translation, rotation, scale, isSoft, allowDuplicates, useSaved); } -void MyAvatar::setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visible) { - if (model->isActive() && model->isRenderable()) { - model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE, true); - } -} - void MyAvatar::initHeadBones() { int neckJointIndex = -1; if (_skeletonModel->isLoaded()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 28af8b62fd..a62bc1a109 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -677,8 +677,6 @@ private: // These are made private for MyAvatar so that you will use the "use" methods instead virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; - void setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visiblity); - virtual void updatePalms() override {} void lateUpdatePalms(); diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index f8685df5da..a9bb12d087 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -124,6 +124,7 @@ protected: bool _isFading{ _entitiesShouldFadeFunction() }; bool _prevIsTransparent { false }; bool _visible { false }; + bool _canCastShadow { false }; bool _cauterized { false }; bool _moving { false }; bool _needsRenderUpdate { false }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 56e3f96014..d3e1b62f5a 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1356,6 +1356,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } // TODO? early exit here when not visible? + if (model->canCastShadow() != _canCastShadow) { + model->setCanCastShadow(_canCastShadow, scene); + } + if (_needsCollisionGeometryUpdate) { setCollisionMeshKey(entity->getCollisionMeshKey()); _needsCollisionGeometryUpdate = false; diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 54dfd96a00..74b278c2c1 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -204,6 +204,7 @@ void CauterizedModel::updateRenderItems() { bool isWireframe = self->isWireframe(); bool isVisible = self->isVisible(); + bool canCastShadow = self->canCastShadow(); bool isLayeredInFront = self->isLayeredInFront(); bool isLayeredInHUD = self->isLayeredInHUD(); bool enableCauterization = self->getEnableCauterization(); @@ -219,7 +220,7 @@ void CauterizedModel::updateRenderItems() { bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex); transaction.updateItem(itemID, [modelTransform, clusterTransforms, clusterTransformsCauterized, invalidatePayloadShapeKey, - isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) { + isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, canCastShadow, enableCauterization](CauterizedMeshPartPayload& data) { data.updateClusterBuffer(clusterTransforms, clusterTransformsCauterized); Transform renderTransform = modelTransform; @@ -249,7 +250,7 @@ void CauterizedModel::updateRenderItems() { data.updateTransformForCauterizedMesh(renderTransform); data.setEnableCauterization(enableCauterization); - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, render::ItemKey::TAG_BITS_ALL); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, render::ItemKey::TAG_BITS_ALL); data.setLayer(isLayeredInFront, isLayeredInHUD); data.setShapeKey(invalidatePayloadShapeKey, isWireframe); }); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index da11535396..84f6c6aca3 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -71,7 +71,7 @@ void MeshPartPayload::updateMaterial(graphics::MaterialPointer drawMaterial) { _drawMaterial = drawMaterial; } -void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { +void MeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); @@ -85,6 +85,10 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, builder.withLayered(); } + if (canCastShadow) { + builder.withShadowCaster(); + } + if (isGroupCulled) { builder.withSubMetaCulled(); } @@ -421,6 +425,10 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag builder.withLayered(); } + if (canCastShadow) { + builder.withShadowCaster(); + } + if (isGroupCulled) { builder.withSubMetaCulled(); } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 40efc67572..e7996e94dc 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -33,7 +33,7 @@ public: typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - virtual void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false); + virtual void updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled = false); virtual void updateMeshPart(const std::shared_ptr& drawMesh, int partIndex); @@ -99,7 +99,7 @@ public: using TransformType = glm::mat4; #endif - void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false) override; + void updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled = false) override; void updateClusterBuffer(const std::vector& clusterTransforms); void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index bb8353c746..197008fc94 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -102,6 +102,7 @@ Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : _snappedToRegistrationPoint(false), _url(HTTP_INVALID_COM), _isVisible(true), + _canCastShadow(false), _blendNumber(0), _appliedBlendNumber(0), _isWireframe(false) @@ -268,6 +269,7 @@ void Model::updateRenderItems() { bool isWireframe = self->isWireframe(); bool isVisible = self->isVisible(); + bool canCastShadow = self->canCastShadow(); uint8_t viewTagBits = self->getViewTagBits(); bool isLayeredInFront = self->isLayeredInFront(); bool isLayeredInHUD = self->isLayeredInHUD(); @@ -284,7 +286,7 @@ void Model::updateRenderItems() { transaction.updateItem(itemID, [modelTransform, clusterTransforms, invalidatePayloadShapeKey, isWireframe, isVisible, - viewTagBits, isLayeredInFront, + canCastShadow, viewTagBits, isLayeredInFront, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { data.updateClusterBuffer(clusterTransforms); @@ -301,7 +303,7 @@ void Model::updateRenderItems() { } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); data.setShapeKey(invalidatePayloadShapeKey, isWireframe); }); @@ -693,46 +695,68 @@ void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, bool isLayeredInFront = _isLayeredInFront; bool isLayeredInHUD = _isLayeredInHUD; - + bool canCastShadow = _canCastShadow; render::Transaction transaction; foreach (auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); }); } scene->enqueueTransaction(transaction); } } +void Model::setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene) { + if (_canCastShadow != canCastShadow) { + _canCastShadow = canCastShadow; + + bool isVisible = _isVisible; + bool viewTagBits = _viewTagBits; + bool isLayeredInFront = _isLayeredInFront; + bool isLayeredInHUD = _isLayeredInHUD; + bool isGroupCulled = _isGroupCulled; + + render::Transaction transaction; + foreach (auto item, _modelMeshRenderItemsMap.keys()) { + transaction.updateItem(item, + [isVisible, viewTagBits, canCastShadow, isLayeredInFront, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.updateKey(isVisible, viewTagBits, canCastShadow, isLayeredInFront || isLayeredInHUD, isGroupCulled); + }); + } + + scene->enqueueTransaction(transaction); + } +} void Model::setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene) { if (_isLayeredInFront != isLayeredInFront) { _isLayeredInFront = isLayeredInFront; bool isVisible = _isVisible; + bool canCastShadow = _canCastShadow; uint8_t viewTagBits = _viewTagBits; bool isLayeredInHUD = _isLayeredInHUD; bool isGroupCulled = _isGroupCulled; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } @@ -745,22 +769,23 @@ void Model::setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& sce _isLayeredInHUD = isLayeredInHUD; bool isVisible = _isVisible; + bool canCastShadow = _canCastShadow; uint8_t viewTagBits = _viewTagBits; bool isLayeredInFront = _isLayeredInFront; bool isGroupCulled = _isGroupCulled; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, + transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, canCastShadow, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, canCastShadow, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 57d2798a66..c0d2c32f8f 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -87,6 +87,10 @@ public: // new Scene/Engine rendering support void setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled); + + bool canCastShadow() const { return _canCastShadow; } + void setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene); + void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene); void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene); bool needsFixupInScene() const; @@ -401,6 +405,8 @@ protected: bool _isVisible; uint8_t _viewTagBits{ render::ItemKey::TAG_BITS_ALL }; + bool _canCastShadow; + gpu::Buffers _blendedVertexBuffers; QVector > > _dilatedTextures; From 47924a58f9732e62ef79efa065bd944781193ebd Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 16 Feb 2018 10:14:46 -0800 Subject: [PATCH 35/80] WIP - for review. --- libraries/render-utils/src/MeshPartPayload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 84f6c6aca3..178f5782dd 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -411,7 +411,7 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render _worldBound.transform(boundTransform); } -void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { +void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); From 4c1f22f84e62213b3de4e7cb658a0473809a37d3 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 16 Feb 2018 14:01:33 -0800 Subject: [PATCH 36/80] Models and Avatar cast shadows (box doesn't, yet). --- interface/src/avatar/MyAvatar.cpp | 5 +++- .../src/RenderableEntityItem.cpp | 1 + libraries/entities/src/EntityItem.cpp | 28 +++++++++++++++++++ libraries/entities/src/EntityItem.h | 5 ++++ libraries/entities/src/ModelEntityItem.cpp | 28 ------------------- libraries/entities/src/ModelEntityItem.h | 5 ---- .../render-utils/src/MeshPartPayload.cpp | 2 +- 7 files changed, 39 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 97b8c71a32..c70dc9df98 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1112,7 +1112,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); - _skeletonModel->setCanCastShadow(true, qApp->getMain3DScene()); + _skeletonModel->setCanCastShadow(isEnabled, qApp->getMain3DScene()); } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { @@ -2010,8 +2010,11 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { _attachmentData[i].jointName.compare("RightEye", Qt::CaseInsensitive) == 0 || _attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 || _attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) { + _attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); + + _attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene()); } } } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index aca2f4d35b..7f2f57d1ac 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -371,6 +371,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); + _canCastShadow = entity->getCanCastShadow(); _cauterized = entity->getCauterized(); _needsRenderUpdate = false; }); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index ed13a46414..e14d0b6757 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -99,6 +99,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_REGISTRATION_POINT; requestedProperties += PROP_ANGULAR_DAMPING; requestedProperties += PROP_VISIBLE; + requestedProperties += PROP_CAN_CAST_SHADOW; requestedProperties += PROP_COLLISIONLESS; requestedProperties += PROP_COLLISION_MASK; requestedProperties += PROP_DYNAMIC; @@ -257,6 +258,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible()); + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, getCollisionless()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, getCollisionMask()); APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, getDynamic()); @@ -807,6 +809,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, setAngularDamping); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY(PROP_COLLISIONLESS, bool, setCollisionless); READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint8_t, setCollisionMask); READ_ENTITY_PROPERTY(PROP_DYNAMIC, bool, setDynamic); @@ -900,6 +903,7 @@ void EntityItem::debugDump() const { qCDebug(entities, " edited ago:%f", (double)getEditedAgo()); qCDebug(entities, " position:%f,%f,%f", (double)position.x, (double)position.y, (double)position.z); qCDebug(entities) << " dimensions:" << getScaledDimensions(); + qCDebug(entities) << " can cast shadow" << getCanCastShadow(); } // adjust any internal timestamps to fix clock skew for this server @@ -1242,6 +1246,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRenderAlpha, getLocalRenderAlpha); COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionless, getCollisionless); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionMask, getCollisionMask); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dynamic, getDynamic); @@ -1354,6 +1359,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); // Certifiable Properties @@ -2731,6 +2737,28 @@ void EntityItem::setVisible(bool value) { } } +bool EntityItem::getCanCastShadow() const { + bool result; + withReadLock([&] { + result = _canCastShadow; + }); + return result; +} + +void EntityItem::setCanCastShadow(bool value) { + bool changed = false; + withWriteLock([&] { + if (_canCastShadow != value) { + changed = true; + _canCastShadow = value; + } + }); + + if (changed) { + emit requestRenderUpdate(); + } +} + bool EntityItem::isChildOfMyAvatar() const { QUuid ancestorID = findAncestorOfType(NestableType::Avatar); return !ancestorID.isNull() && (ancestorID == Physics::getSessionUUID() || ancestorID == AVATAR_SELF_ID); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 5f84bcc311..19a1a9c88e 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -273,6 +273,10 @@ public: bool getVisible() const; void setVisible(bool value); + + bool getCanCastShadow() const; + void setCanCastShadow(bool value); + inline bool isVisible() const { return getVisible(); } inline bool isInvisible() const { return !getVisible(); } @@ -543,6 +547,7 @@ protected: glm::vec3 _registrationPoint { ENTITY_ITEM_DEFAULT_REGISTRATION_POINT }; float _angularDamping { ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING }; bool _visible { ENTITY_ITEM_DEFAULT_VISIBLE }; + bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; bool _collisionless { ENTITY_ITEM_DEFAULT_COLLISIONLESS }; uint8_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT }; bool _dynamic { ENTITY_ITEM_DEFAULT_DYNAMIC }; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index b1edd47a67..a4fe8e6b1e 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -54,7 +54,6 @@ void ModelEntityItem::setTextures(const QString& textures) { EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getModelURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); @@ -74,7 +73,6 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); @@ -116,7 +114,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, const unsigned char* dataAt = data; bool animationPropertiesChanged = false; - READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); @@ -153,7 +150,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_CAN_CAST_SHADOW; requestedProperties += PROP_MODEL_URL; requestedProperties += PROP_COMPOUND_SHAPE_URL; requestedProperties += PROP_TEXTURES; @@ -178,7 +174,6 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, getModelURL()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); @@ -295,7 +290,6 @@ void ModelEntityItem::updateFrameCount() { } void ModelEntityItem::debugDump() const { - qCDebug(entities) << " can cast shadow" << getCanCastShadow(); qCDebug(entities) << "ModelEntityItem id:" << getEntityItemID(); qCDebug(entities) << " edited ago:" << getEditedAgo(); qCDebug(entities) << " position:" << getWorldPosition(); @@ -728,25 +722,3 @@ bool ModelEntityItem::isAnimatingSomething() const { (_animationProperties.getFPS() != 0.0f); }); } - -bool ModelEntityItem::getCanCastShadow() const { - bool result; - withReadLock([&] { - result = _canCastShadow; - }); - return result; -} - -void ModelEntityItem::setCanCastShadow(bool value) { - bool changed = false; - withWriteLock([&] { - if (_canCastShadow != value) { - changed = true; - _canCastShadow = value; - } - }); - - if (changed) { - emit requestRenderUpdate(); - } -} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 791eebb7d9..c2109ba51f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -131,9 +131,6 @@ public: QVector getJointTranslations() const; QVector getJointTranslationsSet() const; - bool getCanCastShadow() const; - void setCanCastShadow(bool value); - private: void setAnimationSettings(const QString& value); // only called for old bitstream format ShapeType computeTrueShapeType() const; @@ -174,8 +171,6 @@ protected: ShapeType _shapeType = SHAPE_TYPE_NONE; - bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; - private: uint64_t _lastAnimated{ 0 }; AnimationPropertyGroup _previousAnimationProperties; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 84f6c6aca3..178f5782dd 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -411,7 +411,7 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render _worldBound.transform(boundTransform); } -void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { +void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); From dc3b03c9bcbd3df163cacfabdb6701a2dad3055b Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 16 Feb 2018 18:37:47 -0800 Subject: [PATCH 37/80] Added shadows to shapes - still sucks... --- libraries/entities-renderer/src/RenderableEntityItem.cpp | 4 ++-- libraries/render/src/render/Item.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 7f2f57d1ac..1318f221c9 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -159,10 +159,10 @@ Item::Bound EntityRenderer::getBound() { ItemKey EntityRenderer::getKey() { if (isTransparent()) { - return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).withNoShadowCaster(); } - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).withShadowCaster(); } uint32_t EntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) { diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index e4dcc7ee03..bd7daa228e 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -123,6 +123,7 @@ public: Builder& withDeformed() { _flags.set(DEFORMED); return (*this); } Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); } Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } + Builder& withNoShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } Builder& withLayered() { _flags.set(LAYERED); return (*this); } Builder& withMetaCullGroup() { _flags.set(META_CULL_GROUP); return (*this); } Builder& withSubMetaCulled() { _flags.set(SUB_META_CULLED); return (*this); } From 7ba8a9935a5d18c2448e203c3556f1bd59ddbab2 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Fri, 16 Feb 2018 19:23:31 -0800 Subject: [PATCH 38/80] No extraneous shadows. --- libraries/render-utils/src/MeshPartPayload.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 178f5782dd..293150d552 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -87,6 +87,8 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShad if (canCastShadow) { builder.withShadowCaster(); + } else { + builder.withNoShadowCaster(); } if (isGroupCulled) { @@ -427,6 +429,8 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCas if (canCastShadow) { builder.withShadowCaster(); + } else { + builder.withNoShadowCaster(); } if (isGroupCulled) { From a69816f885146389750438f8a69caad5c62d297e Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Sat, 17 Feb 2018 15:21:57 -0800 Subject: [PATCH 39/80] WIP. --- libraries/entities/src/EntityItem.cpp | 1 - .../entities/src/EntityItemProperties.cpp | 36 ++++++++----------- .../src/EntityItemPropertiesDefaults.h | 4 +-- libraries/entities/src/EntityPropertyFlags.h | 2 +- libraries/entities/src/ShapeEntityItem.cpp | 27 -------------- libraries/entities/src/ShapeEntityItem.h | 3 -- 6 files changed, 18 insertions(+), 55 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index e14d0b6757..3b118a1376 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -903,7 +903,6 @@ void EntityItem::debugDump() const { qCDebug(entities, " edited ago:%f", (double)getEditedAgo()); qCDebug(entities, " position:%f,%f,%f", (double)position.x, (double)position.y, (double)position.z); qCDebug(entities) << " dimensions:" << getScaledDimensions(); - qCDebug(entities) << " can cast shadow" << getCanCastShadow(); } // adjust any internal timestamps to fix clock skew for this server diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 78c6c6a6b3..60f0034d81 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -458,6 +458,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_VELOCITY, angularVelocity); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_DAMPING, angularDamping); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE, visible); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISIONLESS, collisionless); COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_COLLISIONLESS, collisionless, ignoreForCollisions, getCollisionless()); // legacy support COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_MASK, collisionMask); @@ -626,10 +627,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); } - // Models and Shapes - if (_type == EntityTypes::Model || _type == EntityTypes::Shape || _type == EntityTypes::Box || _type == EntityTypes::Sphere) { - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); - } + ////// Models and Shapes + ////if (_type == EntityTypes::Model || _type == EntityTypes::Shape || _type == EntityTypes::Box || _type == EntityTypes::Sphere) { + //// COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); + ////} if (!skipDefaults && !strictSemantics) { AABox aaBox = getAABox(); @@ -1364,6 +1365,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, properties.getAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, properties.getAngularDamping()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, properties.getVisible()); + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, properties.getCollisionless()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, properties.getCollisionMask()); APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, properties.getDynamic()); @@ -1506,14 +1508,14 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } - // Only models and shapes (including cubes and spheres) can cast shadows - if (properties.getType() == EntityTypes::Model || - properties.getType() == EntityTypes::Shape || - properties.getType() == EntityTypes::Box || - properties.getType() == EntityTypes::Sphere) { - - APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); - } + ////// Only models and shapes (including cubes and spheres) can cast shadows + ////if (properties.getType() == EntityTypes::Model || + //// properties.getType() == EntityTypes::Shape || + //// properties.getType() == EntityTypes::Box || + //// properties.getType() == EntityTypes::Sphere) { + //// + //// APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); + ////} APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); @@ -1734,6 +1736,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, glm::vec3, setAngularVelocity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONLESS, bool, setCollisionless); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_MASK, uint8_t, setCollisionMask); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DYNAMIC, bool, setDynamic); @@ -1871,15 +1874,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); } - // Can cast shadow flag - if (properties.getType() == EntityTypes::Model || - properties.getType() == EntityTypes::Shape || - properties.getType() == EntityTypes::Box || - properties.getType() == EntityTypes::Sphere) { - - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); - } - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index efbf45ce8d..7a8db2591f 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -46,8 +46,8 @@ const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; -const bool ENTITY_ITEM_DEFAULT_CAST_SHADOWS { true }; -const bool ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW { false }; +const bool ENTITY_ITEM_DEFAULT_CAST_SHADOWS { false }; +const bool ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW { true }; const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); const quint64 ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP = 0; diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index ab17f2a873..3f542adfc2 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -20,6 +20,7 @@ enum EntityPropertyList { // these properties are supported by the EntityItem base class PROP_VISIBLE, + PROP_CAN_CAST_SHADOW, PROP_POSITION, PROP_DIMENSIONS, PROP_ROTATION, @@ -31,7 +32,6 @@ enum EntityPropertyList { PROP_SCRIPT, // these properties are supported by some derived classes - PROP_CAN_CAST_SHADOW, PROP_COLOR, // these are used by models only diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 9d81e850df..10588aa643 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -91,7 +91,6 @@ EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredP EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class properties.setColor(getXColor()); properties.setShape(entity::stringFromShape(getShape())); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); return properties; } @@ -131,7 +130,6 @@ bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); if (somethingChanged) { bool wantDebug = false; @@ -157,7 +155,6 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape); READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); - READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); return bytesRead; } @@ -169,7 +166,6 @@ EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_SHAPE; requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; - requestedProperties += PROP_CAN_CAST_SHADOW; return requestedProperties; } @@ -186,7 +182,6 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); - APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); } void ShapeEntityItem::setColor(const rgbColor& value) { @@ -369,25 +364,3 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { ShapeType ShapeEntityItem::getShapeType() const { return _collisionShapeType; } - -bool ShapeEntityItem::getCanCastShadow() const { - bool result; - withReadLock([&] { - result = _canCastShadow; - }); - return result; -} - -void ShapeEntityItem::setCanCastShadow(bool value) { - bool changed = false; - withWriteLock([&] { - if (_canCastShadow != value) { - changed = true; - _canCastShadow = value; - } - }); - - if (changed) { - emit requestRenderUpdate(); - } -} diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 4bc008f761..f5677830c7 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -101,9 +101,6 @@ public: virtual void computeShapeInfo(ShapeInfo& info) override; virtual ShapeType getShapeType() const override; - bool getCanCastShadow() const; - void setCanCastShadow(bool value); - protected: float _alpha { 1 }; From 3027d3461e446eca1901527206cdc064dd8efca8 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 17 Feb 2018 15:24:50 -0800 Subject: [PATCH 40/80] Qt 5.10 seems to indicate pendingDatagramSizes of 0. Reading from these would block and causes an QAbstractSocket::TemporaryError to be thrown --- libraries/networking/src/udt/Socket.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 55643985c8..f705854bda 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -328,14 +328,14 @@ void Socket::checkForReadyReadBackup() { void Socket::readPendingDatagrams() { int packetSizeWithHeader = -1; - while ((packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { + while ((packetSizeWithHeader = _udpSocket.pendingDatagramSize()) > 0) { // we're reading a packet so re-start the readyRead backup timer _readyReadBackupTimer->start(); // grab a time point we can mark as the receive time of this packet auto receiveTime = p_high_resolution_clock::now(); - + // setup a HifiSockAddr to read into HifiSockAddr senderSockAddr; From 0a92596452250881bec62a2faf9785603db6bf89 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 18 Feb 2018 10:04:22 -0800 Subject: [PATCH 41/80] don't rely on return value of pendingDatagramSize() when hasPendingDatagrams() is false --- libraries/networking/src/udt/Socket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index f705854bda..af9ff76eb3 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -328,7 +328,7 @@ void Socket::checkForReadyReadBackup() { void Socket::readPendingDatagrams() { int packetSizeWithHeader = -1; - while ((packetSizeWithHeader = _udpSocket.pendingDatagramSize()) > 0) { + while (_udpSocket.hasPendingDatagrams() && (packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { // we're reading a packet so re-start the readyRead backup timer _readyReadBackupTimer->start(); From 81c9e449e69248b7e6369d7681a41778774c8fa1 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Sun, 18 Feb 2018 10:19:17 -0800 Subject: [PATCH 42/80] Models casting shadows (again). (Something stupid happened...) --- libraries/entities/src/ShapeEntityItem.cpp | 1 - libraries/render/src/render/Item.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 10588aa643..c9ba96d06d 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -261,7 +261,6 @@ void ShapeEntityItem::debugDump() const { qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); qCDebug(entities) << "SHAPE EntityItem Ptr:" << this; - qCDebug(entities) << " can cast shadow" << getCanCastShadow(); } void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index bd7daa228e..25c1244990 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -123,7 +123,7 @@ public: Builder& withDeformed() { _flags.set(DEFORMED); return (*this); } Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); } Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } - Builder& withNoShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } + Builder& withNoShadowCaster() { _flags.reset(SHADOW_CASTER); return (*this); } Builder& withLayered() { _flags.set(LAYERED); return (*this); } Builder& withMetaCullGroup() { _flags.set(META_CULL_GROUP); return (*this); } Builder& withSubMetaCulled() { _flags.set(SUB_META_CULLED); return (*this); } From 6a9bbaf0a55175d3886d4a73c1f210c4909f613e Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 19 Feb 2018 12:00:30 -0800 Subject: [PATCH 43/80] WIP - clean-up. --- libraries/entities-renderer/src/RenderableEntityItem.cpp | 2 +- libraries/render-utils/src/MeshPartPayload.cpp | 5 +---- libraries/render-utils/src/RenderShadowTask.cpp | 3 +++ libraries/render/src/render/Item.h | 2 -- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 1318f221c9..b1251a2530 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -159,7 +159,7 @@ Item::Bound EntityRenderer::getBound() { ItemKey EntityRenderer::getKey() { if (isTransparent()) { - return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).withNoShadowCaster(); + return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); } return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).withShadowCaster(); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 293150d552..16eca007af 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -87,8 +87,6 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShad if (canCastShadow) { builder.withShadowCaster(); - } else { - builder.withNoShadowCaster(); } if (isGroupCulled) { @@ -413,6 +411,7 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render _worldBound.transform(boundTransform); } +// Note that this method is called for models but not for shapes void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCastShadow, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); @@ -429,8 +428,6 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, bool canCas if (canCastShadow) { builder.withShadowCaster(); - } else { - builder.withNoShadowCaster(); } if (isGroupCulled) { diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 7806c95330..be84482c63 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -223,7 +223,10 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto setupOutput = task.addJob("ShadowSetup"); const auto queryResolution = setupOutput.getN(2); // Fetch and cull the items from the scene + + // Enable models to not cast shadows (otherwise, models will always cast shadows) static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withShadowCaster(); + const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 25c1244990..f6cd6a19e1 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -123,7 +123,6 @@ public: Builder& withDeformed() { _flags.set(DEFORMED); return (*this); } Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); } Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } - Builder& withNoShadowCaster() { _flags.reset(SHADOW_CASTER); return (*this); } Builder& withLayered() { _flags.set(LAYERED); return (*this); } Builder& withMetaCullGroup() { _flags.set(META_CULL_GROUP); return (*this); } Builder& withSubMetaCulled() { _flags.set(SUB_META_CULLED); return (*this); } @@ -227,7 +226,6 @@ public: Builder& withVisible() { _value.reset(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); } Builder& withInvisible() { _value.set(ItemKey::INVISIBLE); _mask.set(ItemKey::INVISIBLE); return (*this); } - Builder& withNoShadowCaster() { _value.reset(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); } Builder& withShadowCaster() { _value.set(ItemKey::SHADOW_CASTER); _mask.set(ItemKey::SHADOW_CASTER); return (*this); } Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } From 992121780703dd586bf97ee74ffaf01f086a2aa1 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Mon, 19 Feb 2018 12:32:11 -0800 Subject: [PATCH 44/80] Shapes casting shadows OK. --- libraries/entities-renderer/src/RenderableEntityItem.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index b1251a2530..6483a7e891 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -162,7 +162,12 @@ ItemKey EntityRenderer::getKey() { return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); } - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).withShadowCaster(); + // This allows shapes to cast shadows + if (_canCastShadow) { + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1).withShadowCaster(); + } else { + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1); + } } uint32_t EntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) { From 5cd184ddc3d5403eca4bea7a70f830c2f7ef381e Mon Sep 17 00:00:00 2001 From: Liv Erickson Date: Tue, 20 Feb 2018 17:55:30 -0800 Subject: [PATCH 45/80] change default add to world shape for asset browser --- interface/resources/qml/hifi/AssetServer.qml | 2 +- interface/resources/qml/hifi/dialogs/TabletAssetServer.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index dd0aba9ec5..fea275999e 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -206,7 +206,7 @@ Windows.ScrollingWindow { SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; - var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_SIMPLE_COMPOUND; var DYNAMIC_DEFAULT = false; var prompt = desktop.customInputDialog({ textInput: { diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 049a7faad8..c5f85e885c 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -207,7 +207,7 @@ Rectangle { SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; - var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_SIMPLE_COMPOUND; var DYNAMIC_DEFAULT = false; var prompt = tabletRoot.customInputDialog({ textInput: { From 6f84e4c9772627dbb1c511896f571977c8886d6f Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Tue, 20 Feb 2018 23:45:07 -0800 Subject: [PATCH 46/80] Moved shadows and ambient occlusion to menu checkboxes. --- .../dialogs/GraphicsPreferencesDialog.qml | 19 ---------- .../hifi/tablet/TabletGraphicsPreferences.qml | 37 ------------------- interface/src/Menu.cpp | 14 +++++-- interface/src/Menu.h | 2 + .../src/DeferredLightingEffect.cpp | 2 +- 5 files changed, 13 insertions(+), 61 deletions(-) delete mode 100644 interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml delete mode 100644 interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml diff --git a/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml deleted file mode 100644 index d95bafd0a9..0000000000 --- a/interface/resources/qml/hifi/dialogs/GraphicsPreferencesDialog.qml +++ /dev/null @@ -1,19 +0,0 @@ -import QtQuick 2.5 -import Qt.labs.settings 1.0 - -import "../../dialogs" - -PreferencesDialog { - id: root - objectName: "GraphicsPreferencesDialog" - title: "Graphics Settings" - showCategories: ["Graphics"] - property var settings: Settings { - category: root.objectName - property alias x: root.x - property alias y: root.y - property alias width: root.width - property alias height: root.height - } -} - diff --git a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml deleted file mode 100644 index 25b5be05f2..0000000000 --- a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml +++ /dev/null @@ -1,37 +0,0 @@ -// -// TabletGraphicsPreferences.qml -// -// Created by Vlad Stelmahovsky on 12 Mar 2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "tabletWindows" -import "../../dialogs" - -StackView { - id: profileRoot - initialItem: root - objectName: "stack" - property string title: "Graphics Settings" - - signal sendToScript(var message); - - function pushSource(path) { - profileRoot.push(Qt.resolvedUrl(path)); - } - - function popSource() { - profileRoot.pop(); - } - - TabletPreferencesDialog { - id: root - objectName: "TabletGraphicsPreferences" - showCategories: ["Graphics"] - } -} diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index f3d8ea2344..051d5ce8ec 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -44,6 +44,7 @@ #include "ui/StandAloneJSConsole.h" #include "InterfaceLogging.h" #include "LocationBookmarks.h" +#include "DeferredLightingEffect.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" @@ -365,10 +366,15 @@ Menu::Menu() { MenuWrapper* developerMenu = addMenu("Developer", "Developer"); // Developer > Graphics... - action = addActionToQMenuAndActionHash(developerMenu, "Graphics..."); - connect(action, &QAction::triggered, [] { - qApp->showDialog(QString("hifi/dialogs/GraphicsPreferencesDialog.qml"), - QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog"); + MenuWrapper* graphicsOptionsMenu = developerMenu->addMenu("Render"); + action = addCheckableActionToQMenuAndActionHash(graphicsOptionsMenu, MenuOption::Shadows, 0, true); + connect(action, &QAction::triggered, [action] { + DependencyManager::get()->setShadowMapEnabled(action->isChecked()); + }); + + action = addCheckableActionToQMenuAndActionHash(graphicsOptionsMenu, MenuOption::AmbientOcclusion, 0, false); + connect(action, &QAction::triggered, [action] { + DependencyManager::get()->setAmbientOcclusionEnabled(action->isChecked()); }); // Developer > UI >>> diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 8cb1804fd4..ec6fa42818 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -204,6 +204,8 @@ namespace MenuOption { const QString WorldAxes = "World Axes"; const QString DesktopTabletToToolbar = "Desktop Tablet Becomes Toolbar"; const QString HMDTabletToToolbar = "HMD Tablet Becomes Toolbar"; + const QString Shadows = "Shadows"; + const QString AmbientOcclusion = "AmbientOcclusion"; } #endif // hifi_Menu_h diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 665e767c7c..78bc3ba195 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -492,7 +492,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, batch.setResourceTexture(DEFERRED_BUFFER_DEPTH_UNIT, deferredFramebuffer->getPrimaryDepthTexture()); // FIXME: Different render modes should have different tasks - if (args->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && deferredLightingEffect->isAmbientOcclusionEnabled()) { + if (args->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && deferredLightingEffect->isAmbientOcclusionEnabled() && ambientOcclusionFramebuffer) { batch.setResourceTexture(DEFERRED_BUFFER_OBSCURANCE_UNIT, ambientOcclusionFramebuffer->getOcclusionTexture()); } else { // need to assign the white texture if ao is off From 47a98d9d0f49dd2621102c34475bae4112d50419 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 21 Feb 2018 10:12:04 -0800 Subject: [PATCH 47/80] relax the deadlock time to 2 minutes --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3aa0f3d889..2b87456459 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -392,7 +392,7 @@ const QHash Application::_acceptedExtensi class DeadlockWatchdogThread : public QThread { public: static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1; - static const unsigned long MAX_HEARTBEAT_AGE_USECS = 30 * USECS_PER_SECOND; + static const unsigned long MAX_HEARTBEAT_AGE_USECS = 120 * USECS_PER_SECOND; // 2 mins with no checkin probably a deadlock static const int WARNING_ELAPSED_HEARTBEAT = 500 * USECS_PER_MSEC; // warn if elapsed heartbeat average is large static const int HEARTBEAT_SAMPLES = 100000; // ~5 seconds worth of samples From bf4ba07b722fb4e2f1990e7a445794f828037d97 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 21 Feb 2018 12:36:34 -0800 Subject: [PATCH 48/80] Added parameters to canCastShadow - still sucks. --- interface/src/avatar/MyAvatar.cpp | 6 +++--- .../entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- libraries/render-utils/src/Model.cpp | 4 +--- libraries/render-utils/src/Model.h | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c70dc9df98..2a2e4a1199 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1112,7 +1112,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { void MyAvatar::setEnableMeshVisible(bool isEnabled) { _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); - _skeletonModel->setCanCastShadow(isEnabled, qApp->getMain3DScene()); + _skeletonModel->setCanCastShadow(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { @@ -1465,7 +1465,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { int skeletonModelChangeCount = _skeletonModelChangeCount; Avatar::setSkeletonModelURL(skeletonModelURL); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); - _skeletonModel->setCanCastShadow(true, qApp->getMain3DScene()); + _skeletonModel->setCanCastShadow(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); _headBoneSet.clear(); _cauterizationNeedsUpdate = true; @@ -2014,7 +2014,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { _attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); - _attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene()); + _attachmentModels[i]->setCanCastShadow(shouldDrawHead, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); } } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 9dfa7d4796..1a7af15374 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1357,7 +1357,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // TODO? early exit here when not visible? if (model->canCastShadow() != _canCastShadow) { - model->setCanCastShadow(_canCastShadow, scene); + model->setCanCastShadow(_canCastShadow, scene, viewTaskBits, false); } if (_needsCollisionGeometryUpdate) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 11955c4be5..386f890153 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -713,15 +713,13 @@ void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, } } -void Model::setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene) { +void Model::setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled) { if (_canCastShadow != canCastShadow) { _canCastShadow = canCastShadow; bool isVisible = _isVisible; - bool viewTagBits = _viewTagBits; bool isLayeredInFront = _isLayeredInFront; bool isLayeredInHUD = _isLayeredInHUD; - bool isGroupCulled = _isGroupCulled; render::Transaction transaction; foreach (auto item, _modelMeshRenderItemsMap.keys()) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index b7015b8732..f301dd8031 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -89,7 +89,7 @@ public: void setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled); bool canCastShadow() const { return _canCastShadow; } - void setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene); + void setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled); void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene); void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene); From 049beba1572638b2b45695dfee3a3e5c5f2b8c86 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 21 Feb 2018 14:15:41 -0800 Subject: [PATCH 49/80] Corrected edge case of creating a snapshot with "" as the filename. --- interface/src/Application.h | 3 +-- interface/src/scripting/WindowScriptingInterface.h | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index d4041aa3be..784b509c1a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -266,9 +266,8 @@ public: float getGameLoopRate() const { return _gameLoopCounter.rate(); } - // Note that takeSnapshot has a default value, as this method is used internally. void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString()); - void takeSecondaryCameraSnapshot(const QString& filename); + void takeSecondaryCameraSnapshot(const QString& filename = QString()); void shareSnapshot(const QString& filename, const QUrl& href = QUrl("")); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 76decf4362..196863e362 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -334,8 +334,8 @@ public slots: * @param {number} aspectRatio=0 - The width/height ratio of the snapshot required. If the value is 0 the * full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the * dimensions is adjusted in order to match the aspect ratio. - * @param {string} filename=QString() - If this value is not null then the image will be saved to this filename, with an appended ",jpg". - * otherwise, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS' + * @param {string} filename="" - If this value is not given, or is "" then the image will be saved to this filename, with an appended ",jpg". + * otherwise, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. * @example Using the snapshot function and signals. * function onStillSnapshotTaken(path, notify) { * print("Still snapshot taken: " + path); @@ -357,7 +357,7 @@ public slots: * var notify = true; * var animated = true; * var aspect = 1920 / 1080; - * var filename = QString(); + * var filename = ""; * Window.takeSnapshot(notify, animated, aspect, filename); */ void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString()); @@ -365,7 +365,7 @@ public slots: /**jsdoc * Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API. * @function Window.takeSecondaryCameraSnapshot - * @param {string} filename=QString() - If this value is not null then the image will be saved to this filename, with an appended ".jpg" + * @param {string} filename="" - If this value is not given, or is "" then the image will be saved to this filename, with an appended ".jpg". * * var filename = QString(); */ From 4b1217f1f6b4768386b8b9f7ab09a2efb31b5d8a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Wed, 21 Feb 2018 15:25:18 -0800 Subject: [PATCH 50/80] move all entity items to main thread --- interface/src/octree/OctreePacketProcessor.cpp | 2 ++ libraries/entities/src/EntityTypes.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index c792834d9c..122b58c057 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -17,6 +17,8 @@ #include "SceneScriptingInterface.h" OctreePacketProcessor::OctreePacketProcessor() { + setObjectName("Octree Packet Processor"); + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerDirectListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 307371c649..694542b04e 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -97,6 +97,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const auto mutableProperties = properties; mutableProperties.markAllChanged(); newEntityItem = factory(entityID, mutableProperties); + newEntityItem->moveToThread(qApp->thread()); } return newEntityItem; } From a843a534277cdc3d2885bb5531d93a8e93bb3143 Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Thu, 22 Feb 2018 10:17:20 -0800 Subject: [PATCH 51/80] Improved JSDoc comments. --- interface/src/scripting/WindowScriptingInterface.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 196863e362..9d773b8a2f 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -325,6 +325,7 @@ public slots: * {@link Window.stillSnapshotTaken|stillSnapshotTaken} is emitted; when a still image plus moving images are captured, * {@link Window.processingGifStarted|processingGifStarted} and {@link Window.processingGifCompleted|processingGifCompleted} * are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings > + * NOTE: to provide a non-default value - all previous parameters must be provided. * General > Snapshots. * @function Window.takeSnapshot * @param {boolean} notify=true - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} @@ -334,8 +335,10 @@ public slots: * @param {number} aspectRatio=0 - The width/height ratio of the snapshot required. If the value is 0 the * full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the * dimensions is adjusted in order to match the aspect ratio. - * @param {string} filename="" - If this value is not given, or is "" then the image will be saved to this filename, with an appended ",jpg". - * otherwise, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. + * @param {string} filename="" - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. + * If this parameter is "" then the image will be saved as ".jpg". + * Otherwise, the image will be saved to this filename, with an appended ".jpg". + * * @example Using the snapshot function and signals. * function onStillSnapshotTaken(path, notify) { * print("Still snapshot taken: " + path); @@ -364,8 +367,11 @@ public slots: /**jsdoc * Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API. + * NOTE: to provide a non-default value - all previous parameters must be provided. * @function Window.takeSecondaryCameraSnapshot - * @param {string} filename="" - If this value is not given, or is "" then the image will be saved to this filename, with an appended ".jpg". + * @param {string} filename="" - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. + * If this parameter is "" then the image will be saved as ".jpg". + * Otherwise, the image will be saved to this filename, with an appended ".jpg". * * var filename = QString(); */ From c15cf33ca1f9a5ec776838dd1c19c976a57319c0 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Fri, 23 Feb 2018 00:36:00 +0300 Subject: [PATCH 52/80] 12518 - Help icon in toolbar does not highlight when help window is open --- scripts/system/help.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/help.js b/scripts/system/help.js index 494b0a2cdb..cae5a11d79 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -15,6 +15,7 @@ (function() { // BEGIN LOCAL_SCOPE var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; + var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html"; var buttonName = "HELP"; var onHelpScreen = false; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -40,7 +41,7 @@ } function onScreenChanged(type, url) { - onHelpScreen = type === "Web" && url.startsWith("../../../html/tabletHelp.html"); + onHelpScreen = type === "Web" && url.startsWith(HELP_URL); button.editProperties({ isActive: onHelpScreen }); } From 4d2d7fa51f5d8cd37d43ff5f245cf03cb41af110 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 22 Feb 2018 13:56:46 -0800 Subject: [PATCH 53/80] lots of fun initial progress! --- interface/resources/fonts/hifi-glyphs.ttf | Bin 32536 -> 32544 bytes .../qml/hifi/commerce/checkout/Checkout.qml | 8 +- .../hifi/commerce/purchases/PurchasedItem.qml | 3 + interface/src/commerce/QmlCommerce.cpp | 99 ++++++++++++++++++ interface/src/commerce/QmlCommerce.h | 8 +- scripts/system/marketplaces/marketplaces.js | 10 +- 6 files changed, 120 insertions(+), 8 deletions(-) diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 8db0377f88864810e7f893132449ad8e258e68cd..7f7393da1811f3933a2d9c0deabfdbc245a25b47 100644 GIT binary patch delta 216 zcmbR7k8#02Mhyl=1_lORh6V;^h5$FW5Z}t>Df<{0>RA{V7-ZaCT-_M6H9y?UR&-AV_+!z!N9<{xgfu| zq-^uwW(J0eH4F@F`e2I~85p9{zJ$f|+k9ob(8U_Y7h((MH4DuVzTjTj{zA|t#F@V6e#qS@(=uM1=N;&5$a&vHUo3S!) qK3!(X$ZW`9FqyqVkIB$rvw6i-HV*d8yyB9?oSeyb>MXgD^a23O9zII| diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index ab47bb28ad..98f26887ae 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -146,7 +146,8 @@ Rectangle { } onItemTypeChanged: { - if (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "avatar") { + if (root.itemType === "entity" || root.itemType === "wearable" || + root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") { root.isCertified = true; } else { root.isCertified = false; @@ -679,7 +680,7 @@ Rectangle { id: rezNowButton; enabled: (root.itemType === "entity" && root.canRezCertifiedItems) || (root.itemType === "contentSet" && Entities.canReplaceContent()) || - root.itemType === "wearable" || root.itemType === "avatar"; + root.itemType === "wearable" || root.itemType === "avatar" || root.itemType === "app"; buttonGlyph: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; color: hifi.buttons.red; colorScheme: hifi.colorSchemes.light; @@ -712,6 +713,9 @@ Rectangle { lightboxPopup.button2text = "CONFIRM"; lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + root.itemHref + "'); root.visible = false;"; lightboxPopup.visible = true; + } else if (root.itemType === "app") { + // "Run" button is separate. + Commerce.installApp(root.itemHref); } else { sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); rezzedNotifContainer.visible = true; diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index cc2bcd69aa..c96fc15f5c 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -506,6 +506,9 @@ Item { sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref}); } else if (root.itemType === "avatar") { sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref}); + } else if (root.itemType === "app") { + // "Run" and "Uninstall" buttons are separate. + Commerce.installApp(root.itemHref); } else { sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); root.showConfirmation = true; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 36c1e422c5..0b583e6153 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -10,6 +10,7 @@ // #include "QmlCommerce.h" +#include "CommerceLogging.h" #include "Application.h" #include "DependencyManager.h" #include "Ledger.h" @@ -17,6 +18,7 @@ #include #include #include +#include QmlCommerce::QmlCommerce() { auto ledger = DependencyManager::get(); @@ -183,3 +185,100 @@ void QmlCommerce::alreadyOwned(const QString& marketplaceId) { auto ledger = DependencyManager::get(); ledger->alreadyOwned(marketplaceId); } + +static QString APP_PATH = PathUtils::getAppDataPath() + "apps"; +bool QmlCommerce::isAppInstalled(const QString& itemHref) { + QUrl appHref(itemHref); + + QFileInfo appFile(APP_PATH + "/" + appHref.fileName()); + if (appFile.exists() && appFile.isFile()) { + return true; + } else { + return false; + } +} + +bool QmlCommerce::installApp(const QString& itemHref) { + if (!QDir(APP_PATH).exists()) { + if (!QDir().mkdir(APP_PATH)) { + qCDebug(commerce) << "Couldn't make APP_PATH directory."; + return false; + } + } + + QUrl appHref(itemHref); + + auto request = + std::unique_ptr(DependencyManager::get()->createResourceRequest(this, appHref)); + + if (!request) { + qCDebug(commerce) << "Couldn't create resource request for app."; + return false; + } + + QEventLoop loop; + connect(request.get(), &ResourceRequest::finished, &loop, &QEventLoop::quit); + request->send(); + loop.exec(); + + if (request->getResult() != ResourceRequest::Success) { + qCDebug(commerce) << "Failed to get .app.json file from remote."; + return false; + } + + // Copy the .app.json to the apps directory inside %AppData%/High Fidelity/Interface + auto requestData = request->getData(); + QFile appFile(APP_PATH + "/" + appHref.fileName()); + if (!appFile.open(QIODevice::WriteOnly)) { + qCDebug(commerce) << "Couldn't open local .app.json file for creation."; + return false; + } + if (appFile.write(requestData) == -1) { + qCDebug(commerce) << "Couldn't write to local .app.json file."; + return false; + } + // Close the file + appFile.close(); + + // Read from the returned datastream to know what .js to add to Running Scripts + QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(requestData); + QJsonObject appFileJsonObject = appFileJsonDocument.object(); + QString scriptUrl = appFileJsonObject["scriptURL"].toString(); + + if ((DependencyManager::get()->loadScript(scriptUrl.trimmed())).isNull()) { + qCDebug(commerce) << "Couldn't load script."; + return false; + } + + emit appInstalled(appHref.fileName()); + return true; +} + +bool QmlCommerce::uninstallApp(const QString& itemHref) { + QUrl appHref(itemHref); + + // Read from the file to know what .js script to stop + QFile appFile(APP_PATH + "/" + appHref.fileName()); + if (!appFile.open(QIODevice::ReadOnly)) { + qCDebug(commerce) << "Couldn't open local .app.json file for deletion."; + return false; + } + QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); + QJsonObject appFileJsonObject = appFileJsonDocument.object(); + QString scriptUrl = appFileJsonObject["scriptURL"].toString(); + + if (!DependencyManager::get()->stopScript(scriptUrl.trimmed(), false)) { + qCDebug(commerce) << "Couldn't stop script."; + return false; + } + + // Delete the .app.json from the filesystem + // remove() closes the file first. + if (!appFile.remove()) { + qCDebug(commerce) << "Couldn't delete local .app.json file."; + return false; + } + + emit appUninstalled(appHref.fileName()); + return true; +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index b621608190..60e52a441b 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -51,6 +51,9 @@ signals: void contentSetChanged(const QString& contentSetHref); + void appInstalled(const QString& appFileName); + void appUninstalled(const QString& appFileName); + protected: Q_INVOKABLE void getWalletStatus(); @@ -76,8 +79,11 @@ protected: Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage); Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage); - Q_INVOKABLE void replaceContentSet(const QString& itemHref); + + Q_INVOKABLE bool isAppInstalled(const QString& itemHref); + Q_INVOKABLE bool installApp(const QString& itemHref); + Q_INVOKABLE bool uninstallApp(const QString& itemHref); }; #endif // hifi_QmlCommerce_h diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 631b5e97ac..ecd1bf2a6e 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -551,11 +551,11 @@ var selectionDisplay = null; // for gridTool.js to ignore break; case 'checkout_rezClicked': case 'purchases_rezClicked': - if (message.itemType === "app") { - console.log("How did you get here? You can't buy apps yet!"); - } else { - rezEntity(message.itemHref, message.itemType); - } + rezEntity(message.itemHref, message.itemType); + break; + case 'checkout_installClicked': + case 'purchases_installClicked': + break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': From bb04f3f53328906baa542c16f8f6f9d1a22f249a Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 22 Feb 2018 13:57:46 -0800 Subject: [PATCH 54/80] CR corrections. --- .../entities/src/EntityItemProperties.cpp | 2 +- .../src/EntityItemPropertiesDefaults.h | 1 - libraries/entities/src/EntityPropertyFlags.h | 2 +- .../entities/src/KeyLightPropertyGroup.cpp | 19 +++++++++---------- .../entities/src/KeyLightPropertyGroup.h | 2 +- scripts/system/html/entityProperties.html | 8 ++++---- 6 files changed, 16 insertions(+), 18 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 0612810e1e..2eca612fc2 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1192,7 +1192,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_DIRECTION, KeyLightDirection, keyLightDirection, glm::vec3); - ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_CAST_SHADOWS, KeyLightCastShadows, keyLightCastShadows, bool); + ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_CAST_SHADOW, KeyLightCastShadows, keyLightCastShadows, bool); ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray); diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 7a8db2591f..0e0c2994cd 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -46,7 +46,6 @@ const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; -const bool ENTITY_ITEM_DEFAULT_CAST_SHADOWS { false }; const bool ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW { true }; const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index b787b16fb9..07908fe6cf 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -209,7 +209,7 @@ enum EntityPropertyList { PROP_KEYLIGHT_COLOR, PROP_KEYLIGHT_INTENSITY, PROP_KEYLIGHT_DIRECTION, - PROP_KEYLIGHT_CAST_SHADOWS, + PROP_KEYLIGHT_CAST_SHADOW, PROP_HAZE_RANGE, PROP_HAZE_COLOR, diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index 70b9a5395a..b2c65c6f9d 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -29,7 +29,7 @@ void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desired COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_COLOR, KeyLight, keyLight, Color, color); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_INTENSITY, KeyLight, keyLight, Intensity, intensity); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_DIRECTION, KeyLight, keyLight, Direction, direction); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_CAST_SHADOWS, KeyLight, keyLight, CastShadows, castShadows); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_CAST_SHADOW, KeyLight, keyLight, CastShadows, castShadows); } void KeyLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { @@ -88,14 +88,13 @@ bool KeyLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, getCastShadows()); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, getCastShadows()); return true; } bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, - int& processedBytes) -{ + int& processedBytes) { int bytesRead = 0; bool overwriteLocalData = true; @@ -104,12 +103,12 @@ bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFl READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, bool, setCastShadows); + READ_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, bool, setCastShadows); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_COLOR, Color); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_INTENSITY, Intensity); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_DIRECTION, Direction); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_CAST_SHADOWS, CastShadows); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_CAST_SHADOW, CastShadows); processedBytes += bytesRead; @@ -131,7 +130,7 @@ EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_COLOR, color); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_INTENSITY, intensity); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, direction); - CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_CAST_SHADOWS, castShadows); + CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_CAST_SHADOW, castShadows); return changedProperties; } @@ -160,7 +159,7 @@ EntityPropertyFlags KeyLightPropertyGroup::getEntityProperties(EncodeBitstreamPa requestedProperties += PROP_KEYLIGHT_COLOR; requestedProperties += PROP_KEYLIGHT_INTENSITY; requestedProperties += PROP_KEYLIGHT_DIRECTION; - requestedProperties += PROP_KEYLIGHT_CAST_SHADOWS; + requestedProperties += PROP_KEYLIGHT_CAST_SHADOW; return requestedProperties; } @@ -178,7 +177,7 @@ void KeyLightPropertyGroup::appendSubclassData(OctreePacketData* packetData, Enc APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, getCastShadows()); + APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, getCastShadows()); } int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, @@ -192,7 +191,7 @@ int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, bool, setCastShadows); + READ_ENTITY_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, bool, setCastShadows); return bytesRead; } diff --git a/libraries/entities/src/KeyLightPropertyGroup.h b/libraries/entities/src/KeyLightPropertyGroup.h index d3c8597f95..5e13a6afa6 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.h +++ b/libraries/entities/src/KeyLightPropertyGroup.h @@ -83,7 +83,7 @@ public: DEFINE_PROPERTY_REF(PROP_KEYLIGHT_COLOR, Color, color, xColor, DEFAULT_KEYLIGHT_COLOR); DEFINE_PROPERTY(PROP_KEYLIGHT_INTENSITY, Intensity, intensity, float, DEFAULT_KEYLIGHT_INTENSITY); DEFINE_PROPERTY_REF(PROP_KEYLIGHT_DIRECTION, Direction, direction, glm::vec3, DEFAULT_KEYLIGHT_DIRECTION); - DEFINE_PROPERTY(PROP_KEYLIGHT_CAST_SHADOWS, CastShadows, castShadows, bool, DEFAULT_KEYLIGHT_CAST_SHADOWS); + DEFINE_PROPERTY(PROP_KEYLIGHT_CAST_SHADOW, CastShadows, castShadows, bool, DEFAULT_KEYLIGHT_CAST_SHADOWS); }; #endif // hifi_KeyLightPropertyGroup_h diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 8c34d01358..d6710238fb 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -530,10 +530,10 @@
-
- - -
+
+ + +
Skybox From e9cad947d7f429be3a50e98c0acf5e0feeafd496 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 22 Feb 2018 14:01:56 -0800 Subject: [PATCH 55/80] add the deadlock watchdog stats to the stats metaverse checkin data --- interface/src/Application.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2878515240..8c3e6f130d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1719,6 +1719,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo properties["has_async_reprojection"] = displayPlugin->hasAsyncReprojection(); properties["hardware_stats"] = displayPlugin->getHardwareStats(); + // deadlock watchdog related stats + properties["deadlock_watchdog_maxElapsed"] = (int)DeadlockWatchdogThread::_maxElapsed; + properties["deadlock_watchdog_maxElapsedAverage"] = (int)DeadlockWatchdogThread::_maxElapsedAverage; + auto bandwidthRecorder = DependencyManager::get(); properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond(); properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond(); From de0eee52d6ccaa96d8db21f189d2f0028dd8d33d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 22 Feb 2018 14:45:32 -0800 Subject: [PATCH 56/80] cool --- .../hifi/commerce/purchases/PurchasedItem.qml | 45 +++++++++++++ interface/src/commerce/QmlCommerce.cpp | 67 +++++++++++++++---- interface/src/commerce/QmlCommerce.h | 10 ++- 3 files changed, 107 insertions(+), 15 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index c96fc15f5c..76e2afd308 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -47,6 +47,7 @@ Item { property bool showConfirmation: false; property bool hasPermissionToRezThis; property bool permissionExplanationCardVisible; + property bool isInstalled: false; property string originalStatusText; property string originalStatusColor; @@ -62,6 +63,12 @@ Item { showConfirmation = true; } } + + onAppInstalled: { + if (appHref === root.itemHref) { + root.isInstalled = true; + } + } } Connections { @@ -81,6 +88,10 @@ Item { } else { root.hasPermissionToRezThis = true; } + + if (itemType === "app") { + root.isInstalled = Commerce.isAppInstalled(root.itemHref); + } } onPurchaseStatusChangedChanged: { @@ -472,6 +483,40 @@ Item { } } + Rectangle { + id: appButtonContainer; + color: hifi.colors.white; + z: 994; + visible: root.isInstalled; + anchors.fill: buttonContainer; + + HifiControlsUit.Button { + id: openAppButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.light; + anchors.top: parent.top; + anchors.right: parent.right; + height: 44; + text: "OPEN" + onClicked: { + Commerce.openApp(root.itemHref); + } + } + + HifiControlsUit.Button { + id: uninstallAppButton; + color: hifi.buttons.noneBorderless; + colorScheme: hifi.colorSchemes.light; + anchors.bottom: parent.bottom; + anchors.right: parent.right; + height: 44; + text: "UNINSTALL" + onClicked: { + Commerce.uninstallApp(root.itemHref); + } + } + } + Button { id: buttonContainer; property int color: hifi.buttons.blue; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 0b583e6153..f9bb0d2003 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include "scripting/HMDScriptingInterface.h" QmlCommerce::QmlCommerce() { auto ledger = DependencyManager::get(); @@ -42,6 +44,8 @@ QmlCommerce::QmlCommerce() { connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { setPassphrase(""); }); + + _appsPath = PathUtils::getAppDataPath() + "Apps"; } void QmlCommerce::getWalletStatus() { @@ -186,22 +190,32 @@ void QmlCommerce::alreadyOwned(const QString& marketplaceId) { ledger->alreadyOwned(marketplaceId); } -static QString APP_PATH = PathUtils::getAppDataPath() + "apps"; bool QmlCommerce::isAppInstalled(const QString& itemHref) { QUrl appHref(itemHref); - QFileInfo appFile(APP_PATH + "/" + appHref.fileName()); - if (appFile.exists() && appFile.isFile()) { - return true; - } else { + // First check if .app.json exists + QFileInfo appFile(_appsPath + "/" + appHref.fileName()); + if (!(appFile.exists() && appFile.isFile())) { return false; } + + // Then check to see if script is running + auto runningScripts = DependencyManager::get()->getRunningScripts(); + foreach(const QString& runningScript, runningScripts) { + QUrl runningScriptURL = QUrl(runningScript); + qCDebug(commerce) << "ZRF FIXME" << runningScriptURL; + if (runningScriptURL == appHref) { + return true; + } + } + + return false; } bool QmlCommerce::installApp(const QString& itemHref) { - if (!QDir(APP_PATH).exists()) { - if (!QDir().mkdir(APP_PATH)) { - qCDebug(commerce) << "Couldn't make APP_PATH directory."; + if (!QDir(_appsPath).exists()) { + if (!QDir().mkdir(_appsPath)) { + qCDebug(commerce) << "Couldn't make _appsPath directory."; return false; } } @@ -228,7 +242,7 @@ bool QmlCommerce::installApp(const QString& itemHref) { // Copy the .app.json to the apps directory inside %AppData%/High Fidelity/Interface auto requestData = request->getData(); - QFile appFile(APP_PATH + "/" + appHref.fileName()); + QFile appFile(_appsPath + "/" + appHref.fileName()); if (!appFile.open(QIODevice::WriteOnly)) { qCDebug(commerce) << "Couldn't open local .app.json file for creation."; return false; @@ -250,7 +264,7 @@ bool QmlCommerce::installApp(const QString& itemHref) { return false; } - emit appInstalled(appHref.fileName()); + emit appInstalled(itemHref); return true; } @@ -258,7 +272,7 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { QUrl appHref(itemHref); // Read from the file to know what .js script to stop - QFile appFile(APP_PATH + "/" + appHref.fileName()); + QFile appFile(_appsPath + "/" + appHref.fileName()); if (!appFile.open(QIODevice::ReadOnly)) { qCDebug(commerce) << "Couldn't open local .app.json file for deletion."; return false; @@ -279,6 +293,35 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { return false; } - emit appUninstalled(appHref.fileName()); + emit appUninstalled(itemHref); + return true; +} + +bool QmlCommerce::openApp(const QString& itemHref) { + QUrl appHref(itemHref); + + // Read from the file to know what .html or .qml document to open + QFile appFile(_appsPath + "/" + appHref.fileName()); + if (!appFile.open(QIODevice::ReadOnly)) { + qCDebug(commerce) << "Couldn't open local .app.json file."; + return false; + } + QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); + QJsonObject appFileJsonObject = appFileJsonDocument.object(); + QString homeUrl = appFileJsonObject["homeURL"].toString(); + + auto tabletScriptingInterface = DependencyManager::get(); + auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + if (homeUrl.contains(".qml", Qt::CaseInsensitive)) { + tablet->loadQMLSource(homeUrl); + } else if (homeUrl.contains(".html", Qt::CaseInsensitive)) { + tablet->gotoWebScreen(homeUrl); + } else { + qCDebug(commerce) << "Attempted to open unknown type of homeURL!"; + return false; + } + + DependencyManager::get()->openTablet(); + return true; } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 60e52a441b..e0c018878d 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -81,9 +81,13 @@ protected: Q_INVOKABLE void replaceContentSet(const QString& itemHref); - Q_INVOKABLE bool isAppInstalled(const QString& itemHref); - Q_INVOKABLE bool installApp(const QString& itemHref); - Q_INVOKABLE bool uninstallApp(const QString& itemHref); + Q_INVOKABLE bool isAppInstalled(const QString& appHref); + Q_INVOKABLE bool installApp(const QString& appHref); + Q_INVOKABLE bool uninstallApp(const QString& appHref); + Q_INVOKABLE bool openApp(const QString& appHref); + +private: + QString _appsPath; }; #endif // hifi_QmlCommerce_h From 10fa3fa346a2d8a54b650497f421200cdc4ada17 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 22 Feb 2018 16:09:01 -0800 Subject: [PATCH 57/80] It's working! --- .../hifi/commerce/purchases/PurchasedItem.qml | 15 ++++--- .../qml/hifi/commerce/purchases/Purchases.qml | 12 +++++ interface/src/commerce/QmlCommerce.cpp | 44 ++++++++++++------- interface/src/commerce/QmlCommerce.h | 6 +-- scripts/system/commerce/wallet.js | 2 +- 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 76e2afd308..fb8e509cde 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -47,7 +47,7 @@ Item { property bool showConfirmation: false; property bool hasPermissionToRezThis; property bool permissionExplanationCardVisible; - property bool isInstalled: false; + property bool isInstalled; property string originalStatusText; property string originalStatusColor; @@ -69,6 +69,12 @@ Item { root.isInstalled = true; } } + + onAppUninstalled: { + if (appHref === root.itemHref) { + root.isInstalled = false; + } + } } Connections { @@ -88,10 +94,6 @@ Item { } else { root.hasPermissionToRezThis = true; } - - if (itemType === "app") { - root.isInstalled = Commerce.isAppInstalled(root.itemHref); - } } onPurchaseStatusChangedChanged: { @@ -496,6 +498,8 @@ Item { colorScheme: hifi.colorSchemes.light; anchors.top: parent.top; anchors.right: parent.right; + anchors.left: parent.left; + width: 92; height: 44; text: "OPEN" onClicked: { @@ -509,6 +513,7 @@ Item { colorScheme: hifi.colorSchemes.light; anchors.bottom: parent.bottom; anchors.right: parent.right; + anchors.left: parent.left; height: 44; text: "UNINSTALL" onClicked: { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 9b333a60cd..896200a8e6 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -36,6 +36,7 @@ Rectangle { property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; property int pendingItemCount: 0; + property var installedApps; // Style color: hifi.colors.white; Connections { @@ -61,6 +62,8 @@ Rectangle { root.activeView = "firstUseTutorial"; } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { root.activeView = "purchasesMain"; + root.installedApps = Commerce.getInstalledApps(); + console.log("ZRF! " + root.installedApps); Commerce.inventory(); } } else { @@ -269,6 +272,7 @@ Rectangle { case 'tutorial_finished': Settings.setValue("isFirstUseOfPurchases", false); root.activeView = "purchasesMain"; + root.installedApps = Commerce.getInstalledApps(); Commerce.inventory(); break; } @@ -394,6 +398,7 @@ Rectangle { limitedRun: model.limited_run; displayedItemCount: model.displayedItemCount; permissionExplanationCardVisible: model.permissionExplanationCardVisible; + isInstalled: model.isInstalled; itemType: { if (model.root_file_url.indexOf(".fst") > -1) { "avatar"; @@ -680,9 +685,16 @@ Rectangle { if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) { filteredPurchasesModel.clear(); + var currentId; for (var i = 0; i < tempPurchasesModel.count; i++) { + currentId = tempPurchasesModel.get(i).id; + console.log("ZRF HERE 2 " + root.installedApps); + console.log("ZRF HERE 3 " + currentId); + console.log("ZRF HERE 4 " + ((root.installedApps).indexOf(currentId) > -1)); + filteredPurchasesModel.append(tempPurchasesModel.get(i)); filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false); + filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1)); } populateDisplayedItemCounts(); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index f9bb0d2003..f80e88d175 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -45,7 +45,7 @@ QmlCommerce::QmlCommerce() { setPassphrase(""); }); - _appsPath = PathUtils::getAppDataPath() + "Apps"; + _appsPath = PathUtils::getAppDataPath() + "Apps/"; } void QmlCommerce::getWalletStatus() { @@ -190,26 +190,38 @@ void QmlCommerce::alreadyOwned(const QString& marketplaceId) { ledger->alreadyOwned(marketplaceId); } -bool QmlCommerce::isAppInstalled(const QString& itemHref) { - QUrl appHref(itemHref); +QStringList QmlCommerce::getInstalledApps() { + QStringList installedAppsFromMarketplace; + QStringList runningScripts = DependencyManager::get()->getRunningScripts(); - // First check if .app.json exists - QFileInfo appFile(_appsPath + "/" + appHref.fileName()); - if (!(appFile.exists() && appFile.isFile())) { - return false; - } + QDir directory(_appsPath); + qCDebug(commerce) << "ZRF FIXME" << _appsPath; + QStringList apps = directory.entryList(QStringList("*.app.json")); + foreach(QString appFileName, apps) { + installedAppsFromMarketplace.append(appFileName); + qCDebug(commerce) << "ZRF FIXME" << appFileName; + QFile appFile(_appsPath + appFileName); + if (appFile.open(QIODevice::ReadOnly)) { + QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); - // Then check to see if script is running - auto runningScripts = DependencyManager::get()->getRunningScripts(); - foreach(const QString& runningScript, runningScripts) { - QUrl runningScriptURL = QUrl(runningScript); - qCDebug(commerce) << "ZRF FIXME" << runningScriptURL; - if (runningScriptURL == appHref) { - return true; + appFile.close(); + + QJsonObject appFileJsonObject = appFileJsonDocument.object(); + QString scriptURL = appFileJsonObject["scriptURL"].toString(); + + // If the script .app.json is on the user's local disk but the associated script isn't running + // for some reason, start that script again. + if (!runningScripts.contains(scriptURL)) { + if ((DependencyManager::get()->loadScript(scriptURL.trimmed())).isNull()) { + qCDebug(commerce) << "Couldn't start script while checking installed apps."; + } + } + } else { + qCDebug(commerce) << "Couldn't open local .app.json file for reading."; } } - return false; + return installedAppsFromMarketplace; } bool QmlCommerce::installApp(const QString& itemHref) { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index e0c018878d..2bf4959177 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -51,8 +51,8 @@ signals: void contentSetChanged(const QString& contentSetHref); - void appInstalled(const QString& appFileName); - void appUninstalled(const QString& appFileName); + void appInstalled(const QString& appHref); + void appUninstalled(const QString& appHref); protected: Q_INVOKABLE void getWalletStatus(); @@ -81,7 +81,7 @@ protected: Q_INVOKABLE void replaceContentSet(const QString& itemHref); - Q_INVOKABLE bool isAppInstalled(const QString& appHref); + Q_INVOKABLE QStringList getInstalledApps(); Q_INVOKABLE bool installApp(const QString& appHref); Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 8cf5b72b9a..9ff7038c09 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -39,7 +39,7 @@ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { - tablet.loadQMLSource(WALLET_QML_SOURCE); + tablet.loadQMLSource(MARKETPLACE_PURCHASES_QML_PATH); } } From 33f73fef0df20528ca3867a41d4a1257fc4791f9 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 22 Feb 2018 16:26:35 -0800 Subject: [PATCH 58/80] YAY! --- .../resources/qml/hifi/commerce/purchases/Purchases.qml | 6 +----- interface/src/commerce/QmlCommerce.cpp | 9 ++++----- interface/src/commerce/QmlCommerce.h | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 896200a8e6..3612de7323 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -36,7 +36,7 @@ Rectangle { property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; property int pendingItemCount: 0; - property var installedApps; + property string installedApps; // Style color: hifi.colors.white; Connections { @@ -63,7 +63,6 @@ Rectangle { } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); - console.log("ZRF! " + root.installedApps); Commerce.inventory(); } } else { @@ -688,9 +687,6 @@ Rectangle { var currentId; for (var i = 0; i < tempPurchasesModel.count; i++) { currentId = tempPurchasesModel.get(i).id; - console.log("ZRF HERE 2 " + root.installedApps); - console.log("ZRF HERE 3 " + currentId); - console.log("ZRF HERE 4 " + ((root.installedApps).indexOf(currentId) > -1)); filteredPurchasesModel.append(tempPurchasesModel.get(i)); filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index f80e88d175..6de5de1a9d 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -190,16 +190,15 @@ void QmlCommerce::alreadyOwned(const QString& marketplaceId) { ledger->alreadyOwned(marketplaceId); } -QStringList QmlCommerce::getInstalledApps() { - QStringList installedAppsFromMarketplace; +QString QmlCommerce::getInstalledApps() { + QString installedAppsFromMarketplace; QStringList runningScripts = DependencyManager::get()->getRunningScripts(); QDir directory(_appsPath); - qCDebug(commerce) << "ZRF FIXME" << _appsPath; QStringList apps = directory.entryList(QStringList("*.app.json")); foreach(QString appFileName, apps) { - installedAppsFromMarketplace.append(appFileName); - qCDebug(commerce) << "ZRF FIXME" << appFileName; + installedAppsFromMarketplace += appFileName; + installedAppsFromMarketplace += ","; QFile appFile(_appsPath + appFileName); if (appFile.open(QIODevice::ReadOnly)) { QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 2bf4959177..09eb7137af 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -81,7 +81,7 @@ protected: Q_INVOKABLE void replaceContentSet(const QString& itemHref); - Q_INVOKABLE QStringList getInstalledApps(); + Q_INVOKABLE QString getInstalledApps(); Q_INVOKABLE bool installApp(const QString& appHref); Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); From 80c0f2a21e6f549fff129a460d80b15a3f47d48f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 2 Feb 2018 11:28:25 -0800 Subject: [PATCH 59/80] Fix crash when passing --checkMinSpec flag That flag caused a DLL to be loaded before Application was instanced. This triggers a Qt bug inside Q_COREAPP_STARTUP_FUNC that causes the previous registration pointing the startup function in the main executable to be overridden with the address of the function in the DLL (Since they both link the same static library) This leads to the correct function running in the wrong address space (the DLLs), hence not initializing some global variables correctly. --- assignment-client/src/main.cpp | 3 + domain-server/src/main.cpp | 2 + interface/src/main.cpp | 2 + libraries/shared/src/SettingInterface.cpp | 69 ++++++++++++++--------- libraries/shared/src/SettingManager.cpp | 6 +- libraries/shared/src/SharedUtil.cpp | 9 ++- libraries/shared/src/SharedUtil.h | 34 +---------- tools/ac-client/src/main.cpp | 5 +- tools/atp-client/src/main.cpp | 3 +- tools/oven/src/main.cpp | 8 ++- 10 files changed, 68 insertions(+), 73 deletions(-) diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index 4f64bf8f7f..bf4497984f 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -23,9 +23,12 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + qInstallMessageHandler(LogHandler::verboseMessageHandler); qInfo() << "Starting."; + AssignmentClientApp app(argc, argv); int acReturn = app.exec(); diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index dc3ee54fe7..e258626223 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -29,6 +29,8 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + Setting::init(); #ifndef WIN32 diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 30e8439985..0f8134b253 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -62,6 +62,8 @@ int main(int argc, const char* argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + Setting::init(); // Instance UserActivityLogger now that the settings are loaded diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 327668574e..62f116795e 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -23,43 +23,53 @@ #include "SharedUtil.h" namespace Setting { - static QSharedPointer globalManager; - // cleans up the settings private instance. Should only be run once at closing down. void cleanupPrivateInstance() { + auto globalManager = DependencyManager::get(); + Q_ASSERT(qApp && globalManager); + // grab the thread before we nuke the instance - QThread* settingsManagerThread = DependencyManager::get()->thread(); + QThread* settingsManagerThread = globalManager->thread(); - // tell the private instance to clean itself up on its thread - DependencyManager::destroy(); - - globalManager.reset(); - - // quit the settings manager thread and wait on it to make sure it's gone + // quit the settings manager thread settingsManagerThread->quit(); settingsManagerThread->wait(); + + // Save all settings + globalManager->saveAll(); + + qCDebug(shared) << "Settings thread stopped."; } void setupPrivateInstance() { - // Ensure Setting::init has already ran and qApp exists - if (qApp && globalManager) { - // Let's set up the settings Private instance on its own thread - QThread* thread = new QThread(); - Q_CHECK_PTR(thread); - thread->setObjectName("Settings Thread"); + auto globalManager = DependencyManager::get(); + Q_ASSERT(qApp && globalManager); - QObject::connect(thread, SIGNAL(started()), globalManager.data(), SLOT(startTimer())); - QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - QObject::connect(thread, SIGNAL(finished()), globalManager.data(), SLOT(deleteLater())); - globalManager->moveToThread(thread); - thread->start(); - qCDebug(shared) << "Settings thread started."; + // Let's set up the settings private instance on its own thread + QThread* thread = new QThread(qApp); + Q_CHECK_PTR(thread); + thread->setObjectName("Settings Thread"); - // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. - qAddPostRoutine(cleanupPrivateInstance); - } + // Setup setting periodical save timer + QObject::connect(thread, &QThread::started, globalManager.data(), &Manager::startTimer); + QObject::connect(thread, &QThread::finished, globalManager.data(), &Manager::stopTimer); + + // Setup manager threading affinity + globalManager->moveToThread(thread); + QObject::connect(thread, &QThread::finished, globalManager.data(), [] { + auto globalManager = DependencyManager::get(); + Q_ASSERT(qApp && globalManager); + + // Move manager back to the main thread (has to be done on owning thread) + globalManager->moveToThread(qApp->thread()); + }); + + thread->start(); + qCDebug(shared) << "Settings thread started."; + + // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. + qAddPostRoutine(cleanupPrivateInstance); } - FIXED_Q_COREAPP_STARTUP_FUNCTION(setupPrivateInstance) // Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand, void init() { @@ -68,6 +78,7 @@ namespace Setting { QSettings settings; qCDebug(shared) << "Settings file:" << settings.fileName(); + // Backward compatibility for old settings file if (settings.allKeys().isEmpty()) { loadOldINIFile(settings); } @@ -80,11 +91,13 @@ namespace Setting { qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; } - globalManager = DependencyManager::set(); + // Setup settings manager + DependencyManager::set(); - setupPrivateInstance(); + // Add pre-routine to setup threading + qAddPreRoutine(setupPrivateInstance); } - + void Interface::init() { if (!DependencyManager::isSet()) { // WARNING: As long as we are using QSettings this should always be triggered for each Setting::Handle diff --git a/libraries/shared/src/SettingManager.cpp b/libraries/shared/src/SettingManager.cpp index 6c246d4cea..2e0850255a 100644 --- a/libraries/shared/src/SettingManager.cpp +++ b/libraries/shared/src/SettingManager.cpp @@ -23,11 +23,7 @@ namespace Setting { // Cleanup timer stopTimer(); delete _saveTimer; - - // Save all settings before exit - saveAll(); - - // sync will be called in the QSettings destructor + _saveTimer = nullptr; } // Custom deleter does nothing, because we need to shutdown later than the dependency manager diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 8e5c30711c..772340b631 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -63,12 +63,12 @@ extern "C" FILE * __cdecl __iob_func(void) { #include "OctalCode.h" #include "SharedLogging.h" +static std::mutex stagedGlobalInstancesMutex; static std::unordered_map stagedGlobalInstances; std::mutex& globalInstancesMutex() { - static std::mutex mutex; - return mutex; + return stagedGlobalInstancesMutex; } static void commitGlobalInstances() { @@ -78,7 +78,10 @@ static void commitGlobalInstances() { } stagedGlobalInstances.clear(); } -FIXED_Q_COREAPP_STARTUP_FUNCTION(commitGlobalInstances) + +void setupGlobalInstances() { + qAddPreRoutine(commitGlobalInstances); +} QVariant getGlobalInstance(const char* propertyName) { if (qApp) { diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 5a1e48d9c0..64bbc6585b 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -25,23 +25,6 @@ #include #include -// Workaround for https://bugreports.qt.io/browse/QTBUG-54479 -// Wrap target function inside another function that holds -// a unique string identifier and uses it to ensure it only runs once -// by storing a state within the qApp -// We cannot used std::call_once with a static once_flag because -// this is used in shared libraries that are linked by several DLLs -// (ie. plugins), meaning the static will be useless in that case -#define FIXED_Q_COREAPP_STARTUP_FUNCTION(AFUNC) \ - static void AFUNC ## _fixed() { \ - const auto propertyName = std::string(Q_FUNC_INFO) + __FILE__; \ - if (!qApp->property(propertyName.c_str()).toBool()) { \ - AFUNC(); \ - qApp->setProperty(propertyName.c_str(), QVariant(true)); \ - } \ - } \ - Q_COREAPP_STARTUP_FUNCTION(AFUNC ## _fixed) - // When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows // the value to be reset when the sessionID changes. const QUuid AVATAR_SELF_ID = QUuid("{00000000-0000-0000-0000-000000000001}"); @@ -53,21 +36,7 @@ std::unique_ptr& globalInstancePointer() { return instancePtr; } -template -void setGlobalInstance(const char* propertyName, T* instance) { - globalInstancePointer().reset(instance); -} - -template -bool destroyGlobalInstance() { - std::unique_ptr& instancePtr = globalInstancePointer(); - if (instancePtr.get()) { - instancePtr.reset(); - return true; - } - return false; -} - +void setupGlobalInstances(); std::mutex& globalInstancesMutex(); QVariant getGlobalInstance(const char* propertyName); void setGlobalInstance(const char* propertyName, const QVariant& variant); @@ -78,7 +47,6 @@ void setGlobalInstance(const char* propertyName, const QVariant& variant); template T* globalInstance(const char* propertyName, Args&&... args) { static T* resultInstance { nullptr }; - static std::mutex mutex; if (!resultInstance) { std::unique_lock lock(globalInstancesMutex()); if (!resultInstance) { diff --git a/tools/ac-client/src/main.cpp b/tools/ac-client/src/main.cpp index c9affde3b5..139e44bc9a 100644 --- a/tools/ac-client/src/main.cpp +++ b/tools/ac-client/src/main.cpp @@ -19,15 +19,16 @@ using namespace std; -int main(int argc, char * argv[]) { +int main(int argc, char* argv[]) { QCoreApplication::setApplicationName(BuildInfo::AC_CLIENT_SERVER_NAME); QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + Setting::init(); ACClientApp app(argc, argv); - return app.exec(); } diff --git a/tools/atp-client/src/main.cpp b/tools/atp-client/src/main.cpp index 830c049bc7..0a8274fedd 100644 --- a/tools/atp-client/src/main.cpp +++ b/tools/atp-client/src/main.cpp @@ -25,9 +25,10 @@ int main(int argc, char * argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupGlobalInstances(); + Setting::init(); ATPClientApp app(argc, argv); - return app.exec(); } diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp index 788470b75e..be0c22a0c6 100644 --- a/tools/oven/src/main.cpp +++ b/tools/oven/src/main.cpp @@ -10,11 +10,17 @@ #include "Oven.h" +#include #include +#include int main (int argc, char** argv) { - QCoreApplication::setOrganizationName("High Fidelity"); QCoreApplication::setApplicationName("Oven"); + QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); + QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); + QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + + setupGlobalInstances(); // init the settings interface so we can save and load settings Setting::init(); From 3150b963c0106f163ea5aec03a34a23c5220dda3 Mon Sep 17 00:00:00 2001 From: NissimHadar Date: Thu, 22 Feb 2018 16:29:58 -0800 Subject: [PATCH 60/80] Implemented call to abortTask(). --- .../render-utils/src/RenderShadowTask.cpp | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index be84482c63..ce4bf01dcf 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -120,11 +120,6 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con auto lightStage = renderContext->_scene->getStage(); assert(lightStage); - // Exit if current keylight does not cast shadows - if (!lightStage->getCurrentKeyLight() || !lightStage->getCurrentKeyLight()->getCastShadows()) { - return; - } - auto shadow = lightStage->getCurrentKeyShadow(); if (!shadow || _cascadeIndex >= shadow->getCascadeCount()) { return; @@ -305,8 +300,14 @@ void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) { } void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, Outputs& output) { + // Abort all jobs if not casting shadows auto lightStage = renderContext->_scene->getStage(); assert(lightStage); + if (!lightStage->getCurrentKeyLight() || !lightStage->getCurrentKeyLight()->getCastShadows()) { + renderContext->taskFlow.abortTask(); + return; + } + // Cache old render args RenderArgs* args = renderContext->args; @@ -387,18 +388,6 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon auto lightStage = renderContext->_scene->getStage(); assert(lightStage); - // Exit if current keylight does not cast shadows - if (!lightStage->getCurrentKeyLight()) { - return; - } - - bool castShadows = lightStage->getCurrentKeyLight()->getCastShadows(); - if (!castShadows) { - output.edit0() = ItemFilter::Builder::nothing(); - output.edit1() = ViewFrustumPointer(); - return; - } - // Cache old render args RenderArgs* args = renderContext->args; From f14673bc5079622d019919f004bb54e4539768d9 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 22 Feb 2018 16:37:12 -0800 Subject: [PATCH 61/80] Fixup Checkout for apps --- .../qml/hifi/commerce/checkout/Checkout.qml | 16 +++++++++++++--- scripts/system/commerce/wallet.js | 2 +- scripts/system/marketplaces/marketplaces.js | 4 ---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 98f26887ae..10894109ef 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -48,6 +48,7 @@ Rectangle { property bool debugCheckoutSuccess: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); property string referrer; + property bool isInstalled; // Style color: hifi.colors.white; Connections { @@ -122,6 +123,12 @@ Rectangle { root.refreshBuyUI(); } } + + onAppInstalled: { + if (appHref === root.itemHref) { + root.isInstalled = true; + } + } } onItemIdChanged: { @@ -689,7 +696,7 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]; + text: root.itemType === "app" && root.isInstalled ? "OPEN APP" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)]; onClicked: { if (root.itemType === "contentSet") { lightboxPopup.titleText = "Replace Content"; @@ -714,8 +721,11 @@ Rectangle { lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + root.itemHref + "'); root.visible = false;"; lightboxPopup.visible = true; } else if (root.itemType === "app") { - // "Run" button is separate. - Commerce.installApp(root.itemHref); + if (root.isInstalled) { + Commerce.openApp(root.itemHref); + } else { + Commerce.installApp(root.itemHref); + } } else { sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType}); rezzedNotifContainer.visible = true; diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 9ff7038c09..8cf5b72b9a 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -39,7 +39,7 @@ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { - tablet.loadQMLSource(MARKETPLACE_PURCHASES_QML_PATH); + tablet.loadQMLSource(WALLET_QML_SOURCE); } } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index ecd1bf2a6e..8f51d88f2d 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -552,10 +552,6 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'checkout_rezClicked': case 'purchases_rezClicked': rezEntity(message.itemHref, message.itemType); - break; - case 'checkout_installClicked': - case 'purchases_installClicked': - break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': From 277e556b48666cddee5e04ac181caa1cd969fa8f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 22 Feb 2018 16:22:18 -0800 Subject: [PATCH 62/80] Create a standard function to init Hifi Apps --- assignment-client/src/main.cpp | 16 ++-------------- domain-server/src/main.cpp | 16 +--------------- ice-server/src/main.cpp | 11 +++-------- interface/src/Application.cpp | 7 ++----- interface/src/main.cpp | 11 +---------- libraries/shared/src/LogHandler.cpp | 12 ++++++++++-- libraries/shared/src/SharedUtil.cpp | 20 ++++++++++++++++++++ libraries/shared/src/SharedUtil.h | 1 + tests/entities/src/main.cpp | 3 +++ tests/gpu-test/src/main.cpp | 5 ++++- tests/ktx/src/KtxTests.cpp | 3 ++- tests/qml/src/main.cpp | 12 ++---------- tests/qt59/src/main.cpp | 7 ++----- tests/recording/src/main.cpp | 16 +++++----------- tests/render-perf/src/main.cpp | 18 ++---------------- tests/render-texture-load/src/main.cpp | 16 ++-------------- tests/render-utils/src/main.cpp | 17 +++-------------- tests/shaders/src/main.cpp | 16 +++------------- tools/ac-client/src/main.cpp | 7 +------ tools/atp-client/src/main.cpp | 7 +------ tools/auto-tester/src/main.cpp | 2 +- tools/ice-client/src/main.cpp | 4 ++++ tools/oven/src/main.cpp | 7 +------ tools/skeleton-dump/src/main.cpp | 4 ++++ tools/udt-test/src/UDTTest.cpp | 2 -- tools/udt-test/src/main.cpp | 4 ++++ tools/vhacd-util/src/main.cpp | 4 ++++ 27 files changed, 88 insertions(+), 160 deletions(-) diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index bf4497984f..971e9ed272 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -9,25 +9,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include #include #include "AssignmentClientApp.h" -#include int main(int argc, char* argv[]) { - disableQtBearerPoll(); // Fixes wifi ping spikes - - QCoreApplication::setApplicationName(BuildInfo::ASSIGNMENT_CLIENT_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); - - qInstallMessageHandler(LogHandler::verboseMessageHandler); - qInfo() << "Starting."; - + setupHifiApplication(BuildInfo::ASSIGNMENT_CLIENT_NAME); AssignmentClientApp app(argc, argv); diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index e258626223..d7856bf867 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -22,24 +22,10 @@ #include "DomainServer.h" int main(int argc, char* argv[]) { - disableQtBearerPoll(); // Fixes wifi ping spikes + setupHifiApplication(BuildInfo::DOMAIN_SERVER_NAME); - QCoreApplication::setApplicationName(BuildInfo::DOMAIN_SERVER_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); - Setting::init(); -#ifndef WIN32 - setvbuf(stdout, NULL, _IOLBF, 0); -#endif - - qInstallMessageHandler(LogHandler::verboseMessageHandler); - qInfo() << "Starting."; - int currentExitCode = 0; // use a do-while to handle domain-server restart diff --git a/ice-server/src/main.cpp b/ice-server/src/main.cpp index ec8b9957cf..aac6cc0422 100644 --- a/ice-server/src/main.cpp +++ b/ice-server/src/main.cpp @@ -11,18 +11,13 @@ #include -#include +#include #include "IceServer.h" int main(int argc, char* argv[]) { -#ifndef WIN32 - setvbuf(stdout, NULL, _IOLBF, 0); -#endif - - qInstallMessageHandler(LogHandler::verboseMessageHandler); - qInfo() << "Starting."; + setupHifiApplication("Ice Server"); IceServer iceServer(argc, argv); return iceServer.exec(); -} \ No newline at end of file +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3aa0f3d889..e956195ca6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -575,10 +575,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt QString logMessage = LogHandler::getInstance().printMessage((LogMsgType) type, context, message); if (!logMessage.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(logMessage.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#elif defined Q_OS_ANDROID +#ifdef Q_OS_ANDROID const char * local=logMessage.toStdString().c_str(); switch (type) { case QtDebugMsg: @@ -599,7 +596,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt abort(); } #endif - qApp->getLogger()->addMessage(qPrintable(logMessage + "\n")); + qApp->getLogger()->addMessage(qPrintable(logMessage)); } } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 0f8134b253..e80dc1d213 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -38,6 +38,7 @@ extern "C" { #endif int main(int argc, const char* argv[]) { + setupHifiApplication(BuildInfo::INTERFACE_NAME); #ifdef Q_OS_LINUX QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); @@ -51,19 +52,9 @@ int main(int argc, const char* argv[]) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); #endif - disableQtBearerPoll(); // Fixes wifi ping spikes - QElapsedTimer startupTime; startupTime.start(); - // Set application infos - QCoreApplication::setApplicationName(BuildInfo::INTERFACE_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); - Setting::init(); // Instance UserActivityLogger now that the settings are loaded diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index aa67c14c4b..ff0d68dcce 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -14,6 +14,10 @@ #include +#ifdef Q_OS_WIN +#include +#endif + #include #include #include @@ -184,8 +188,12 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont } } - QString logMessage = QString("%1 %2").arg(prefixString, message.split('\n').join('\n' + prefixString + " ")); - fprintf(stdout, "%s\n", qPrintable(logMessage)); + QString logMessage = QString("%1 %2\n").arg(prefixString, message.split('\n').join('\n' + prefixString + " ")); + + fprintf(stdout, "%s", qPrintable(logMessage)); +#ifdef Q_OS_WIN + OutputDebugStringA(qPrintable(logMessage)); +#endif return logMessage; } diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 772340b631..d05e940322 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -59,6 +59,9 @@ extern "C" FILE * __cdecl __iob_func(void) { #include #include +#include + +#include "LogHandler.h" #include "NumericalConstants.h" #include "OctalCode.h" #include "SharedLogging.h" @@ -1179,6 +1182,23 @@ void watchParentProcess(int parentPID) { } #endif +void setupHifiApplication(QString applicationName) { + disableQtBearerPoll(); // Fixes wifi ping spikes + + QCoreApplication::setApplicationName(applicationName); + QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); + QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); + QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + + setupGlobalInstances(); + +#ifndef WIN32 + setvbuf(stdout, NULL, _IOLBF, 0); +#endif + + qInstallMessageHandler(LogHandler::verboseMessageHandler); + qInfo() << "Starting."; +} #ifdef Q_OS_WIN QString getLastErrorAsString() { diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 64bbc6585b..7cb750d3e3 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -228,6 +228,7 @@ void watchParentProcess(int parentPID); bool processIsRunning(int64_t pid); +void setupHifiApplication(QString applicationName); #ifdef Q_OS_WIN void* createProcessGroup(); diff --git a/tests/entities/src/main.cpp b/tests/entities/src/main.cpp index 792ef7d9c6..bf79f9d3e9 100644 --- a/tests/entities/src/main.cpp +++ b/tests/entities/src/main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include const QString& getTestResourceDir() { static QString dir; @@ -136,6 +137,8 @@ void testPropertyFlags() { } int main(int argc, char** argv) { + setupHifiApplication("Entities Test"); + QCoreApplication app(argc, argv); { auto start = usecTimestampNow(); diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index 6a509afe4e..77ce015e3f 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -192,7 +193,9 @@ void testSparseRectify() { } } -int main(int argc, char** argv) { +int main(int argc, char** argv) { + setupHifiApplication("GPU Test"); + testSparseRectify(); // FIXME this test appears to be broken diff --git a/tests/ktx/src/KtxTests.cpp b/tests/ktx/src/KtxTests.cpp index 94e5d7e8e7..65d9cbec3d 100644 --- a/tests/ktx/src/KtxTests.cpp +++ b/tests/ktx/src/KtxTests.cpp @@ -149,7 +149,8 @@ static const QString TEST_FOLDER { "H:/ktx_cacheold" }; static const QString EXTENSIONS { "*.ktx" }; int mainTemp(int, char**) { - qInstallMessageHandler(messageHandler); + setupHifiApplication("KTX Tests"); + auto fileInfoList = QDir { TEST_FOLDER }.entryInfoList(QStringList { EXTENSIONS }); for (auto fileInfo : fileInfoList) { qDebug() << fileInfo.filePath(); diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index 219efa5996..022f7290f4 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -179,18 +179,10 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { resizeWindow(ev->size()); } -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (!message.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(message.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#endif - } -} +int main(int argc, char** argv) { + setupHifiApplication("QML Test"); -int main(int argc, char** argv) { QGuiApplication app(argc, argv); - qInstallMessageHandler(messageHandler); TestWindow window; app.exec(); return 0; diff --git a/tests/qt59/src/main.cpp b/tests/qt59/src/main.cpp index 7b95cabd6c..19b922de9f 100644 --- a/tests/qt59/src/main.cpp +++ b/tests/qt59/src/main.cpp @@ -63,14 +63,11 @@ void Qt59TestApp::finish(int exitCode) { int main(int argc, char * argv[]) { - QCoreApplication::setApplicationName("Qt59Test"); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + setupHifiApplication("Qt59Test"); Qt59TestApp app(argc, argv); return app.exec(); } -#include "main.moc" \ No newline at end of file +#include "main.moc" diff --git a/tests/recording/src/main.cpp b/tests/recording/src/main.cpp index f4049b04b7..1b4e5adf6d 100644 --- a/tests/recording/src/main.cpp +++ b/tests/recording/src/main.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "Constants.h" using namespace recording; @@ -97,18 +99,10 @@ void testClipOrdering() { Q_UNUSED(lastFrameTimeOffset); // FIXME - Unix build not yet upgraded to Qt 5.5.1 we can remove this once it is } -#ifdef Q_OS_WIN32 -void myMessageHandler(QtMsgType type, const QMessageLogContext & context, const QString & msg) { - OutputDebugStringA(msg.toLocal8Bit().toStdString().c_str()); - OutputDebugStringA("\n"); -} -#endif - int main(int, const char**) { -#ifdef Q_OS_WIN32 - qInstallMessageHandler(myMessageHandler); -#endif + setupHifiApplication("Recording Test"); + testFrameTypeRegistration(); testFilePersist(); testClipOrdering(); -} \ No newline at end of file +} diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 2bf0c74067..ec56555f68 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -1138,31 +1138,17 @@ private: bool QTestWindow::_cullingEnabled = true; -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - QString logMessage = LogHandler::getInstance().printMessage((LogMsgType)type, context, message); - - if (!logMessage.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(logMessage.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#endif - logger->addMessage(qPrintable(logMessage + "\n")); - } -} - const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; int main(int argc, char** argv) { + setupHifiApplication("RenderPerf"); + QApplication app(argc, argv); - QCoreApplication::setApplicationName("RenderPerf"); - QCoreApplication::setOrganizationName("High Fidelity"); - QCoreApplication::setOrganizationDomain("highfidelity.com"); logger.reset(new FileLogger()); - qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QTestWindow::setup(); QTestWindow window; diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp index 066b39fc40..62c970cab5 100644 --- a/tests/render-texture-load/src/main.cpp +++ b/tests/render-texture-load/src/main.cpp @@ -610,16 +610,6 @@ private: bool _ready { false }; }; -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (!message.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(message.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#endif - std::cout << message.toLocal8Bit().constData() << std::endl; - } -} - const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; @@ -645,11 +635,9 @@ void unzipTestData(const QByteArray& zipData) { } int main(int argc, char** argv) { + setupHifiApplication("RenderPerf"); + QApplication app(argc, argv); - QCoreApplication::setApplicationName("RenderPerf"); - QCoreApplication::setOrganizationName("High Fidelity"); - QCoreApplication::setOrganizationDomain("highfidelity.com"); - qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); if (!DATA_DIR.exists()) { diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 741fdbdddd..e30a80f3d9 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -179,25 +179,14 @@ void QTestWindow::draw() { } } -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (!message.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(message.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#else - std::cout << message.toLocal8Bit().constData() << std::endl; -#endif - } -} - - const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; -int main(int argc, char** argv) { +int main(int argc, char** argv) { + setupHifiApplication("Render Utils Test"); + QGuiApplication app(argc, argv); - qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QTestWindow window; QTimer timer; diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index 585bc432a9..a3b4196031 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -214,24 +214,14 @@ void QTestWindow::draw() { _context.swapBuffers(this); } -void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { - if (!message.isEmpty()) { -#ifdef Q_OS_WIN - OutputDebugStringA(message.toLocal8Bit().constData()); - OutputDebugStringA("\n"); -#else - std::cout << message.toLocal8Bit().constData() << std::endl; -#endif - } -} - const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; -int main(int argc, char** argv) { +int main(int argc, char** argv) { + setupHifiApplication("Shaders Test"); + QGuiApplication app(argc, argv); - qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); QTestWindow window; QTimer timer; diff --git a/tools/ac-client/src/main.cpp b/tools/ac-client/src/main.cpp index 139e44bc9a..024a891d3c 100644 --- a/tools/ac-client/src/main.cpp +++ b/tools/ac-client/src/main.cpp @@ -20,12 +20,7 @@ using namespace std; int main(int argc, char* argv[]) { - QCoreApplication::setApplicationName(BuildInfo::AC_CLIENT_SERVER_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); + setupHifiApplication(BuildInfo::AC_CLIENT_SERVER_NAME); Setting::init(); diff --git a/tools/atp-client/src/main.cpp b/tools/atp-client/src/main.cpp index 0a8274fedd..2a267c088c 100644 --- a/tools/atp-client/src/main.cpp +++ b/tools/atp-client/src/main.cpp @@ -20,12 +20,7 @@ using namespace std; int main(int argc, char * argv[]) { - QCoreApplication::setApplicationName(BuildInfo::AC_CLIENT_SERVER_NAME); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); + setupHifiApplication("ATP Client"); Setting::init(); diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp index 45a3743482..6e5e06b732 100644 --- a/tools/auto-tester/src/main.cpp +++ b/tools/auto-tester/src/main.cpp @@ -17,4 +17,4 @@ int main(int argc, char *argv[]) { autoTester.show(); return application.exec(); -} \ No newline at end of file +} diff --git a/tools/ice-client/src/main.cpp b/tools/ice-client/src/main.cpp index c70a7eb7d7..60a5d4e0e4 100644 --- a/tools/ice-client/src/main.cpp +++ b/tools/ice-client/src/main.cpp @@ -13,11 +13,15 @@ #include #include +#include + #include "ICEClientApp.h" using namespace std; int main(int argc, char * argv[]) { + setupHifiApplication("ICE Client"); + ICEClientApp app(argc, argv); return app.exec(); } diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp index be0c22a0c6..3f4afe1f15 100644 --- a/tools/oven/src/main.cpp +++ b/tools/oven/src/main.cpp @@ -15,12 +15,7 @@ #include int main (int argc, char** argv) { - QCoreApplication::setApplicationName("Oven"); - QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); - QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); - QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - - setupGlobalInstances(); + setupHifiApplication("Oven"); // init the settings interface so we can save and load settings Setting::init(); diff --git a/tools/skeleton-dump/src/main.cpp b/tools/skeleton-dump/src/main.cpp index 6cf4d41f31..d5919c4a88 100644 --- a/tools/skeleton-dump/src/main.cpp +++ b/tools/skeleton-dump/src/main.cpp @@ -14,9 +14,13 @@ #include #include +#include + #include "SkeletonDumpApp.h" int main(int argc, char * argv[]) { + setupHifiApplication("Skeleton Dump App"); + SkeletonDumpApp app(argc, argv); return app.getReturnCode(); } diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp index 6161dbfdbc..ce89f04ce5 100644 --- a/tools/udt-test/src/UDTTest.cpp +++ b/tools/udt-test/src/UDTTest.cpp @@ -71,8 +71,6 @@ const QStringList SERVER_STATS_TABLE_HEADERS { UDTTest::UDTTest(int& argc, char** argv) : QCoreApplication(argc, argv) { - qInstallMessageHandler(LogHandler::verboseMessageHandler); - parseArguments(); // randomize the seed for packet size randomization diff --git a/tools/udt-test/src/main.cpp b/tools/udt-test/src/main.cpp index ccb7d0af0f..d88218c0d0 100644 --- a/tools/udt-test/src/main.cpp +++ b/tools/udt-test/src/main.cpp @@ -10,9 +10,13 @@ #include +#include + #include "UDTTest.h" int main(int argc, char* argv[]) { + setupHifiApplication("UDT Test); + UDTTest app(argc, argv); return app.exec(); } diff --git a/tools/vhacd-util/src/main.cpp b/tools/vhacd-util/src/main.cpp index 42c9db9513..817e77bf8e 100644 --- a/tools/vhacd-util/src/main.cpp +++ b/tools/vhacd-util/src/main.cpp @@ -15,6 +15,8 @@ #include #include +#include + #include "VHACDUtilApp.h" using namespace std; @@ -22,6 +24,8 @@ using namespace VHACD; int main(int argc, char * argv[]) { + setupHifiApplication("VHACD Util"); + VHACDUtilApp app(argc, argv); return app.getReturnCode(); } From 249f0568a14157e8a48f0071b65283127e9f93d1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 22 Feb 2018 17:14:23 -0800 Subject: [PATCH 63/80] New loader --- .../qml/hifi/commerce/checkout/Checkout.qml | 7 ++++--- .../hifi/commerce/common/images/loader-blue.gif | Bin 0 -> 59768 bytes 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/common/images/loader-blue.gif diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 10894109ef..d88ded6a15 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -319,7 +319,7 @@ Rectangle { z: 997; visible: !root.ownershipStatusReceived || !root.balanceReceived; anchors.fill: parent; - color: Qt.rgba(0.0, 0.0, 0.0, 0.7); + color: hifi.colors.white; // This object is always used in a popup. // This MouseArea is used to prevent a user from being @@ -331,8 +331,9 @@ Rectangle { } AnimatedImage { - source: "../common/images/loader.gif" - width: 96; + id: loadingImage; + source: "../common/images/loader-blue.gif" + width: 74; height: width; anchors.verticalCenter: parent.verticalCenter; anchors.horizontalCenter: parent.horizontalCenter; diff --git a/interface/resources/qml/hifi/commerce/common/images/loader-blue.gif b/interface/resources/qml/hifi/commerce/common/images/loader-blue.gif new file mode 100644 index 0000000000000000000000000000000000000000..8b9e17053b2420e2ea3dcf68e3677d172858a9b5 GIT binary patch literal 59768 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 Date: Thu, 22 Feb 2018 17:20:36 -0800 Subject: [PATCH 64/80] add dynamic mode for scripted avatar motor --- interface/src/avatar/MyAvatar.cpp | 92 +++++++++++++++++++++++-------- interface/src/avatar/MyAvatar.h | 5 ++ libraries/gl/src/gl/Context.cpp | 2 +- 3 files changed, 75 insertions(+), 24 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c25aaeeecd..445b176c15 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -79,6 +79,8 @@ float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f; const int SCRIPTED_MOTOR_CAMERA_FRAME = 0; const int SCRIPTED_MOTOR_AVATAR_FRAME = 1; const int SCRIPTED_MOTOR_WORLD_FRAME = 2; +const int SCRIPTED_MOTOR_SIMPLE_MODE = 0; +const int SCRIPTED_MOTOR_DYNAMIC_MODE = 1; const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav"; const float MyAvatar::ZOOM_MIN = 0.5f; @@ -92,6 +94,7 @@ MyAvatar::MyAvatar(QThread* thread) : _pitchSpeed(PITCH_SPEED_DEFAULT), _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), + _scriptedMotorMode(SCRIPTED_MOTOR_SIMPLE_MODE), _motionBehaviors(AVATAR_MOTION_DEFAULTS), _characterController(this), _eyeContactTarget(LEFT_EYE), @@ -1623,32 +1626,38 @@ controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action act void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; + glm::quat avatarOrientation = getWorldOrientation(); + + const float FLYING_MOTOR_TIMESCALE = 0.05f; + const float WALKING_MOTOR_TIMESCALE = 0.2f; + const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + + float horizontalMotorTimescale; + float verticalMotorTimescale; + + if (_characterController.getState() == CharacterController::State::Hover || + _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; + verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; + } + else { + horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE; + verticalMotorTimescale = INVALID_MOTOR_TIMESCALE; + } + if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { - - const float FLYING_MOTOR_TIMESCALE = 0.05f; - const float WALKING_MOTOR_TIMESCALE = 0.2f; - const float INVALID_MOTOR_TIMESCALE = 1.0e6f; - - float horizontalMotorTimescale; - float verticalMotorTimescale; - if (_characterController.getState() == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { motorRotation = getMyHead()->getHeadOrientation(); - horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; - verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; } else { // non-hovering = walking: follow camera twist about vertical but not lift // we decompose camera's rotation and store the twist part in motorRotation // however, we need to perform the decomposition in the avatar-frame // using the local UP axis and then transform back into world-frame - glm::quat orientation = getWorldOrientation(); - glm::quat headOrientation = glm::inverse(orientation) * getMyHead()->getHeadOrientation(); // avatar-frame + glm::quat headOrientation = glm::inverse(avatarOrientation) * getMyHead()->getHeadOrientation(); // avatar-frame glm::quat liftRotation; swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation); - motorRotation = orientation * motorRotation; - horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE; - verticalMotorTimescale = INVALID_MOTOR_TIMESCALE; + motorRotation = avatarOrientation * motorRotation; } if (_isPushing || _isBraking || !_isBeingPushed) { @@ -1660,15 +1669,36 @@ void MyAvatar::updateMotors() { } } if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { - if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { - motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); - } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { - motorRotation = getWorldOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); - } else { - // world-frame - motorRotation = glm::quat(); + if (_scriptedMotorMode == SCRIPTED_MOTOR_SIMPLE_MODE) { + if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { + motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); + } + else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { + motorRotation = avatarOrientation * glm::angleAxis(PI, Vectors::UNIT_Y); + } + else { + // world-frame + motorRotation = glm::quat(); + } + _characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale); + } else { + // dynamic mode + glm::vec3 avatarFrameVelocity; + if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { + // convert camera frame velocity to avatar frame + glm::quat cameraOrientation = qApp->getCamera().getOrientation(); + avatarFrameVelocity = glm::inverse(avatarOrientation) * cameraOrientation * _scriptedMotorVelocity; + } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_WORLD_FRAME) { + // convert world frame velocity to avatar frame + avatarFrameVelocity = glm::inverse(avatarOrientation) * _scriptedMotorVelocity; + } else { + // avatar frame + avatarFrameVelocity = _scriptedMotorVelocity; + } + // dynamic mode for scripted motor uses avatar frame and piggybacks off of the default action motor's timescales + motorRotation = avatarOrientation * glm::angleAxis(PI, Vectors::UNIT_Y); + _characterController.addMotor(avatarFrameVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); } - _characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale); } // legacy support for 'MyAvatar::applyThrust()', which has always been implemented as a @@ -1752,6 +1782,14 @@ QString MyAvatar::getScriptedMotorFrame() const { return frame; } +QString MyAvatar::getScriptedMotorMode() const { + QString mode = "simple"; + if (_scriptedMotorMode == SCRIPTED_MOTOR_DYNAMIC_MODE) { + mode = "dynamic"; + } + return mode; +} + void MyAvatar::setScriptedMotorVelocity(const glm::vec3& velocity) { float MAX_SCRIPTED_MOTOR_SPEED = 500.0f; _scriptedMotorVelocity = velocity; @@ -1778,6 +1816,14 @@ void MyAvatar::setScriptedMotorFrame(QString frame) { } } +void MyAvatar::setScriptedMotorMode(QString frame) { + if (frame.toLower() == "simple") { + _scriptedMotorMode = SCRIPTED_MOTOR_SIMPLE_MODE; + } else if (frame.toLower() == "dynamic") { + _scriptedMotorMode = SCRIPTED_MOTOR_DYNAMIC_MODE; + } +} + void MyAvatar::clearScriptableSettings() { _scriptedMotorVelocity = Vectors::ZERO; _scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 28af8b62fd..464d94bce7 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -69,6 +69,7 @@ class MyAvatar : public Avatar { * @property motorTimescale {float} Specifies how quickly the avatar should accelerate to meet the motorVelocity, * smaller values will result in higher acceleration. * @property motorReferenceFrame {string} Reference frame of the motorVelocity, must be one of the following: "avatar", "camera", "world" + * @property motorMode {string} Type of scripted motor behavior, "simple" = unmodified legacy behavior and "dynamic" = same as default motor * @property collisionSoundURL {string} Specifies the sound to play when the avatar experiences a collision. * You can provide a mono or stereo 16-bit WAV file running at either 24 Khz or 48 Khz. * The latter is downsampled by the audio mixer, so all audio effectively plays back at a 24 Khz sample rate. @@ -124,6 +125,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame) + Q_PROPERTY(QString motorMode READ getScriptedMotorMode WRITE setScriptedMotorMode) Q_PROPERTY(QString collisionSoundURL READ getCollisionSoundURL WRITE setCollisionSoundURL) Q_PROPERTY(AudioListenerMode audioListenerMode READ getAudioListenerMode WRITE setAudioListenerMode) Q_PROPERTY(glm::vec3 customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition) @@ -662,9 +664,11 @@ private: glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; } float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; } QString getScriptedMotorFrame() const; + QString getScriptedMotorMode() const; void setScriptedMotorVelocity(const glm::vec3& velocity); void setScriptedMotorTimescale(float timescale); void setScriptedMotorFrame(QString frame); + void setScriptedMotorMode(QString frame); virtual void attach(const QString& modelURL, const QString& jointName = QString(), const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, bool isSoft = false, @@ -706,6 +710,7 @@ private: glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (analog script) float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity int _scriptedMotorFrame; + int _scriptedMotorMode; quint32 _motionBehaviors; QString _collisionSoundURL; diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index 309839808e..155849bffa 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -147,7 +147,7 @@ static void debugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum s } // FIXME For high severity errors, force a sync to the log, since we might crash // before the log file was flushed otherwise. Performance hit here - qCDebug(glLogging) << "OpenGL: " << message; + //qCDebug(glLogging) << "OpenGL: " << message; } static void setupPixelFormatSimple(HDC hdc) { From 9f68accb302e6ea59120a9bb5717df00e2788fc7 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 22 Feb 2018 17:34:00 -0800 Subject: [PATCH 65/80] add menu for testing unresponsive interface --- interface/src/Application.cpp | 12 ++++++++++++ interface/src/Application.h | 1 + interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + 4 files changed, 15 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8c3e6f130d..e8264a398a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7542,6 +7542,18 @@ void Application::deadlockApplication() { } } +// cause main thread to be unresponsive for 35 seconds +void Application::unresponsiveApplication() { + // to avoid compiler warnings about a loop that will never exit + uint64_t start = usecTimestampNow(); + uint64_t UNRESPONSIVE_FOR_SECONDS = 35; + uint64_t UNRESPONSIVE_FOR_USECS = UNRESPONSIVE_FOR_SECONDS * USECS_PER_SECOND; + qCDebug(interfaceapp) << "Intentionally cause Interface to be unresponsive for " << UNRESPONSIVE_FOR_SECONDS << " seconds"; + while (usecTimestampNow() - start < UNRESPONSIVE_FOR_USECS) { + QThread::sleep(1); + } +} + void Application::setActiveDisplayPlugin(const QString& pluginName) { auto menu = Menu::getInstance(); foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { diff --git a/interface/src/Application.h b/interface/src/Application.h index d4041aa3be..5ce9a1619a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -367,6 +367,7 @@ public slots: void updateHeartbeat() const; static void deadlockApplication(); + static void unresponsiveApplication(); // cause main thread to be unresponsive for 35 seconds void rotationModeChanged() const; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 464de87fdb..5abc59356e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -713,6 +713,7 @@ Menu::Menu() { MenuWrapper* crashMenu = developerMenu->addMenu("Crash"); addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication())); + addActionToQMenuAndActionHash(crashMenu, MenuOption::UnresponsiveInterface, 0, qApp, SLOT(unresponsiveApplication())); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction); connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); }); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 8cb1804fd4..9c4efd6ac7 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -77,6 +77,7 @@ namespace MenuOption { const QString CrashNewFault = "New Fault"; const QString CrashNewFaultThreaded = "New Fault (threaded)"; const QString DeadlockInterface = "Deadlock Interface"; + const QString UnresponsiveInterface = "Unresponsive Interface"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DefaultSkybox = "Default Skybox"; const QString DeleteAvatarBookmark = "Delete Avatar Bookmark..."; From ba0ba96d81a096100948d11730b3ff2d7b3825a3 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 22 Feb 2018 17:54:03 -0800 Subject: [PATCH 66/80] undo --- libraries/gl/src/gl/Context.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index 155849bffa..309839808e 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -147,7 +147,7 @@ static void debugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum s } // FIXME For high severity errors, force a sync to the log, since we might crash // before the log file was flushed otherwise. Performance hit here - //qCDebug(glLogging) << "OpenGL: " << message; + qCDebug(glLogging) << "OpenGL: " << message; } static void setupPixelFormatSimple(HDC hdc) { From 0469eafbe46bf2df42f9f0ea6104d1ad9d98273a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 22 Feb 2018 18:35:16 -0800 Subject: [PATCH 67/80] Switch between dualQuats and matrix skinning based on model entity scale factor. --- .../src/RenderableModelEntityItem.cpp | 7 ++++++- .../render-utils/src/MeshPartPayload.cpp | 9 ++++++++- libraries/render-utils/src/MeshPartPayload.h | 2 ++ libraries/render-utils/src/Model.cpp | 20 ++++++++++++------- libraries/render-utils/src/Model.h | 1 + .../render-utils/src/RenderPipelines.cpp | 2 +- libraries/shared/src/DualQuaternion.h | 2 +- libraries/shared/src/GLMHelpers.cpp | 3 +++ libraries/shared/src/GLMHelpers.h | 2 ++ 9 files changed, 37 insertions(+), 11 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 83ec675adf..d295e9e1fa 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -133,6 +133,9 @@ void RenderableModelEntityItem::doInitialModelSimulation() { model->setRotation(getWorldOrientation()); model->setTranslation(getWorldPosition()); + glm::vec3 scale = model->getScale(); + model->setUseDualQuaternionSkinning(!isNonUniformScale(scale)); + if (_needsInitialSimulation) { model->simulate(0.0f); _needsInitialSimulation = false; @@ -243,6 +246,8 @@ void RenderableModelEntityItem::updateModelBounds() { } if (updateRenderItems) { + glm::vec3 scale = model->getScale(); + model->setUseDualQuaternionSkinning(!isNonUniformScale(scale)); model->updateRenderItems(); } } @@ -1500,4 +1505,4 @@ void ModelEntityRenderer::processMaterials() { material.pop(); } } -} \ No newline at end of file +} diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 4a0ea40f55..99048fe324 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -407,7 +407,7 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe if (isWireframe) { builder.withWireframe(); } - if (_useDualQuaternionSkinning) { + if (_useDualQuaternionSkinning && isSkinned) { builder.withDualQuatSkinned(); } @@ -497,3 +497,10 @@ void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterDualQuaternions); + void setUseDualQuaternionSkinning(bool value); + gpu::BufferPointer _clusterBuffer; int _meshIndex; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index da43e4fd8f..8318715732 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -288,6 +288,7 @@ void Model::updateRenderItems() { invalidatePayloadShapeKey, isWireframe, isVisible, viewTagBits, isLayeredInFront, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.setUseDualQuaternionSkinning(useDualQuaternionSkinning); if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); } else { @@ -388,11 +389,8 @@ bool Model::updateGeometry() { const FBXGeometry& fbxGeometry = getFBXGeometry(); foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; - if (_useDualQuaternionSkinning) { - state.clusterDualQuaternions.resize(mesh.clusters.size()); - } else { - state.clusterMatrices.resize(mesh.clusters.size()); - } + state.clusterDualQuaternions.resize(mesh.clusters.size()); + state.clusterMatrices.resize(mesh.clusters.size()); _meshStates.push_back(state); // Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index @@ -1248,6 +1246,10 @@ void Model::snapToRegistrationPoint() { _snappedToRegistrationPoint = true; } +void Model::setUseDualQuaternionSkinning(bool value) { + _useDualQuaternionSkinning = value; +} + void Model::simulate(float deltaTime, bool fullUpdate) { DETAILED_PROFILE_RANGE(simulation_detail, __FUNCTION__); fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit) @@ -1583,11 +1585,13 @@ void Model::addMaterial(graphics::MaterialLayer material, const std::string& par bool wireframe = isWireframe(); auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); + bool useDualQuaternionSkinning = _useDualQuaternionSkinning; transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, - invalidatePayloadShapeKey, wireframe](ModelMeshPartPayload& data) { + invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.addMaterial(material); // if the material changed, we might need to update our item key or shape key data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits); + data.setUseDualQuaternionSkinning(useDualQuaternionSkinning); data.setShapeKey(invalidatePayloadShapeKey, wireframe); }); } @@ -1608,11 +1612,13 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string bool wireframe = isWireframe(); auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); + bool useDualQuaternionSkinning = _useDualQuaternionSkinning; transaction.updateItem(itemID, [material, visible, layeredInFront, layeredInHUD, viewTagBits, - invalidatePayloadShapeKey, wireframe](ModelMeshPartPayload& data) { + invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.removeMaterial(material); // if the material changed, we might need to update our item key or shape key data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits); + data.setUseDualQuaternionSkinning(useDualQuaternionSkinning); data.setShapeKey(invalidatePayloadShapeKey, wireframe); }); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 61e699f30a..aef6a84a24 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -314,6 +314,7 @@ public: void scaleToFit(); bool getUseDualQuaternionSkinning() const { return _useDualQuaternionSkinning; } + void setUseDualQuaternionSkinning(bool value); void addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName); void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName); diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index cccc128b64..3fedae1778 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -447,7 +447,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withSpecular().withFade(), skinModelNormalMapFadeVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); - // dual quatenion skinned + // dual quaternion skinned addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned(), skinModelDualQuatVertex, modelPixel, nullptr, nullptr); diff --git a/libraries/shared/src/DualQuaternion.h b/libraries/shared/src/DualQuaternion.h index 709c089fdc..af1011a6d8 100644 --- a/libraries/shared/src/DualQuaternion.h +++ b/libraries/shared/src/DualQuaternion.h @@ -55,7 +55,7 @@ protected: inline QDebug operator<<(QDebug debug, const DualQuaternion& dq) { - debug << "AnimPose, real = (" << dq._real.x << dq._real.y << dq._real.z << dq._real.w << "), dual = (" << dq._dual.x << dq._dual.y << dq._dual.z << dq._dual.w << ")"; + debug << "DualQuaternion, real = (" << dq._real.x << dq._real.y << dq._real.z << dq._real.w << "), dual = (" << dq._dual.x << dq._dual.y << dq._dual.z << dq._dual.w << ")"; return debug; } diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index ff1d29eed1..46e484cf2b 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -601,3 +601,6 @@ glm::vec3 randVector() { return glm::vec3(randFloat() - 0.5f, randFloat() - 0.5f, randFloat() - 0.5f) * 2.0f; } +bool isNonUniformScale(const glm::vec3& scale) { + return fabsf(scale.x - scale.y) > EPSILON || fabsf(scale.y - scale.z) > EPSILON; +} diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 4f761a4aac..5c9a8b5ca1 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -260,6 +260,8 @@ glm::mat4 orthoInverse(const glm::mat4& m); // Return a random vector of average length 1 glm::vec3 randVector(); +bool isNonUniformScale(const glm::vec3& scale); + // // Safe replacement of glm_mat4_mul() for unaligned arguments instead of __m128 // From b8102cfbd72e3323d35351670c8e5f901cb11c42 Mon Sep 17 00:00:00 2001 From: vladest Date: Fri, 23 Feb 2018 10:06:16 +0100 Subject: [PATCH 68/80] Make sure sort indicator is attached to the right of title --- .../resources/qml/controls-uit/Table.qml | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index a3e4113d08..c5a605c69e 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -22,6 +22,7 @@ TableView { readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light property bool expandSelectedRow: false property bool centerHeaderText: false + readonly property real headerSpacing: 3 //spacing between sort indicator and table header title model: ListModel { } @@ -69,20 +70,18 @@ TableView { height: hifi.dimensions.tableHeaderHeight color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark + RalewayRegular { id: titleText + x: centerHeaderText ? parent.width/2 - + (paintedWidth/2 + (sortIndicatorVisible ? titleSort.paintedWidth/12 : 0)) : + hifi.dimensions.tablePadding text: styleData.value size: hifi.fontSizes.tableHeading font.capitalization: Font.AllUppercase color: hifi.colors.baseGrayHighlight horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft) - anchors { - left: parent.left - leftMargin: hifi.dimensions.tablePadding - right: parent.right - rightMargin: hifi.dimensions.tablePadding - verticalCenter: parent.verticalCenter - } + anchors.verticalCenter: parent.verticalCenter } HiFiGlyphs { @@ -91,13 +90,9 @@ TableView { color: hifi.colors.darkGray opacity: 0.6; size: hifi.fontSizes.tableHeadingIcon - anchors { - left: titleText.right - leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 15 : 10) - right: parent.right - rightMargin: hifi.dimensions.tablePadding - verticalCenter: titleText.verticalCenter - } + anchors.verticalCenter: titleText.verticalCenter + anchors.left: titleText.right + anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 3 + tableView.headerSpacing) visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column } @@ -152,7 +147,7 @@ TableView { color: styleData.selected ? hifi.colors.primaryHighlight : tableView.isLightColorScheme - ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) - : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) + ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) + : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) } } From 59bd05aa554ad6d433d50edae5b223a400a0765f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 23 Feb 2018 09:21:13 -0800 Subject: [PATCH 69/80] make shadow-related shader compile on osx --- libraries/render-utils/src/Shadow.slh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index abb04a4498..36eb35c757 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -89,7 +89,9 @@ float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) { ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); - vec4 cascadeShadowCoords[2] = { vec4(0), vec4(0) }; + vec4 cascadeShadowCoords[2]; + cascadeShadowCoords[0] = vec4(0); + cascadeShadowCoords[1] = vec4(0); ivec2 cascadeIndices; float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); From 5ee28297f33693ce5c90e9fc1745cbdd7f461110 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 23 Feb 2018 10:19:00 -0800 Subject: [PATCH 70/80] Don't use eventLoop, since that will block the UI --- interface/src/commerce/QmlCommerce.cpp | 70 +++++++++++++------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 6de5de1a9d..e7d62930cf 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -233,49 +233,47 @@ bool QmlCommerce::installApp(const QString& itemHref) { QUrl appHref(itemHref); - auto request = - std::unique_ptr(DependencyManager::get()->createResourceRequest(this, appHref)); + auto request = DependencyManager::get()->createResourceRequest(this, appHref); if (!request) { qCDebug(commerce) << "Couldn't create resource request for app."; return false; } - QEventLoop loop; - connect(request.get(), &ResourceRequest::finished, &loop, &QEventLoop::quit); + connect(request, &ResourceRequest::finished, this, [=]() { + if (request->getResult() != ResourceRequest::Success) { + qCDebug(commerce) << "Failed to get .app.json file from remote."; + return false; + } + + // Copy the .app.json to the apps directory inside %AppData%/High Fidelity/Interface + auto requestData = request->getData(); + QFile appFile(_appsPath + "/" + appHref.fileName()); + if (!appFile.open(QIODevice::WriteOnly)) { + qCDebug(commerce) << "Couldn't open local .app.json file for creation."; + return false; + } + if (appFile.write(requestData) == -1) { + qCDebug(commerce) << "Couldn't write to local .app.json file."; + return false; + } + // Close the file + appFile.close(); + + // Read from the returned datastream to know what .js to add to Running Scripts + QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(requestData); + QJsonObject appFileJsonObject = appFileJsonDocument.object(); + QString scriptUrl = appFileJsonObject["scriptURL"].toString(); + + if ((DependencyManager::get()->loadScript(scriptUrl.trimmed())).isNull()) { + qCDebug(commerce) << "Couldn't load script."; + return false; + } + + emit appInstalled(itemHref); + return true; + }); request->send(); - loop.exec(); - - if (request->getResult() != ResourceRequest::Success) { - qCDebug(commerce) << "Failed to get .app.json file from remote."; - return false; - } - - // Copy the .app.json to the apps directory inside %AppData%/High Fidelity/Interface - auto requestData = request->getData(); - QFile appFile(_appsPath + "/" + appHref.fileName()); - if (!appFile.open(QIODevice::WriteOnly)) { - qCDebug(commerce) << "Couldn't open local .app.json file for creation."; - return false; - } - if (appFile.write(requestData) == -1) { - qCDebug(commerce) << "Couldn't write to local .app.json file."; - return false; - } - // Close the file - appFile.close(); - - // Read from the returned datastream to know what .js to add to Running Scripts - QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(requestData); - QJsonObject appFileJsonObject = appFileJsonDocument.object(); - QString scriptUrl = appFileJsonObject["scriptURL"].toString(); - - if ((DependencyManager::get()->loadScript(scriptUrl.trimmed())).isNull()) { - qCDebug(commerce) << "Couldn't load script."; - return false; - } - - emit appInstalled(itemHref); return true; } From d0ec84b6c8739ad6eab55eefe7bc6efd71038eb2 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 23 Feb 2018 10:45:59 -0800 Subject: [PATCH 71/80] remove menu option for booking marking avatar entities --- interface/src/Application.cpp | 4 - interface/src/AvatarBookmarks.cpp | 96 +++++++++++-- interface/src/AvatarBookmarks.h | 1 + interface/src/AvatarEntitiesBookmarks.cpp | 168 ---------------------- interface/src/AvatarEntitiesBookmarks.h | 45 ------ interface/src/Menu.cpp | 5 - interface/src/Menu.h | 1 - 7 files changed, 82 insertions(+), 238 deletions(-) delete mode 100644 interface/src/AvatarEntitiesBookmarks.cpp delete mode 100644 interface/src/AvatarEntitiesBookmarks.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 60e4c7c948..ffd2bbef97 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -68,7 +68,6 @@ #include #include #include -#include #include #include #include @@ -785,7 +784,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(nullptr, qApp->getOcteeSceneStats()); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -2606,7 +2604,6 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get().data()); - surfaceContext->setContextProperty("AvatarEntitiesBookmarks", DependencyManager::get().data()); surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get().data()); // Caches @@ -5987,7 +5984,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get()->getStats().data()); scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get().data()); scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("AvatarEntitiesBookmarks", DependencyManager::get().data()); scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get().data()); scriptEngine->registerGlobalObject("RayPick", DependencyManager::get().data()); diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 0f177e3d15..7845158a80 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -19,6 +19,11 @@ #include #include #include +#include +#include +#include +#include +#include #include "MainWindow.h" #include "Menu.h" @@ -29,6 +34,62 @@ #include + +void addAvatarEntities(const QVariantList& avatarEntities) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + EntityTreePointer entityTree = DependencyManager::get()->getTree(); + if (!entityTree) { + return; + } + EntitySimulationPointer entitySimulation = entityTree->getSimulation(); + PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast(entitySimulation); + EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender(); + QScriptEngine scriptEngine; + for (int index = 0; index < avatarEntities.count(); index++) { + const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap(); + QVariant variantProperties = avatarEntityProperties["properties"]; + QVariantMap asMap = variantProperties.toMap(); + QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); + EntityItemProperties entityProperties; + EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties); + + entityProperties.setParentID(myNodeID); + entityProperties.setClientOnly(true); + entityProperties.setOwningAvatarID(myNodeID); + entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY); + entityProperties.markAllChanged(); + + EntityItemID id = EntityItemID(QUuid::createUuid()); + bool success = true; + entityTree->withWriteLock([&] { + EntityItemPointer entity = entityTree->addEntity(id, entityProperties); + if (entity) { + if (entityProperties.queryAACubeRelatedPropertyChanged()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + bool success; + AACube queryAACube = entity->getQueryAACube(success); + if (success) { + entityProperties.setQueryAACube(queryAACube); + } + } + + entity->setLastBroadcast(usecTimestampNow()); + // since we're creating this object we will immediately volunteer to own its simulation + entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); + entityProperties.setLastEdited(entity->getLastEdited()); + } else { + qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree"; + success = false; + } + }); + + if (success) { + entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties); + } + } +} + AvatarBookmarks::AvatarBookmarks() { _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME; readFromFile(); @@ -38,7 +99,7 @@ void AvatarBookmarks::readFromFile() { // migrate old avatarbookmarks.json, used to be in 'local' folder on windows QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME; QFile oldConfig(oldConfigPath); - + // I imagine that in a year from now, this code for migrating (as well as the two lines above) // may be removed since all bookmarks should have been migrated by then // - Robbie Uvanni (6.8.2017) @@ -48,9 +109,9 @@ void AvatarBookmarks::readFromFile() { } else { qCDebug(interfaceapp) << "Failed to migrate" << AVATARBOOKMARKS_FILENAME; } - } - - Bookmarks::readFromFile(); + } + + Bookmarks::readFromFile(); } void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { @@ -81,23 +142,27 @@ void AvatarBookmarks::changeToBookmarkedAvatar() { myAvatar->useFullAvatarURL(action->data().toString()); qCDebug(interfaceapp) << " Using Legacy V1 Avatar Bookmark "; } else { - + const QMap bookmark = action->data().toMap(); - // Not magic value. This is the current made version, and if it changes this interpreter should be updated to + // Not magic value. This is the current made version, and if it changes this interpreter should be updated to // handle the new one separately. // This is where the avatar bookmark entry is parsed. If adding new Value, make sure to have backward compatability with previous if (bookmark.value(ENTRY_VERSION) == 3) { - const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); - myAvatar->useFullAvatarURL(avatarUrl); - qCDebug(interfaceapp) << "Avatar On " << avatarUrl; - const QList& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList()).toList(); + myAvatar->removeAvatarEntities(); + const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); + myAvatar->useFullAvatarURL(avatarUrl); + qCDebug(interfaceapp) << "Avatar On " << avatarUrl; + const QList& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList()).toList(); - qCDebug(interfaceapp) << "Attach " << attachments; - myAvatar->setAttachmentsVariant(attachments); + qCDebug(interfaceapp) << "Attach " << attachments; + myAvatar->setAttachmentsVariant(attachments); + + const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); + myAvatar->setAvatarScale(qScale); + + const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); + addAvatarEntities(avatarEntities); - const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); - myAvatar->setAvatarScale(qScale); - } else { qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarBookmark"; } @@ -126,6 +191,7 @@ void AvatarBookmarks::addBookmark() { bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale); bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant()); + bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant()); Bookmarks::addBookmarkToFile(bookmarkName, bookmark); }); diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index 0529eeb516..7e2f64379e 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -34,6 +34,7 @@ private: const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json"; const QString ENTRY_AVATAR_URL = "avatarUrl"; const QString ENTRY_AVATAR_ATTACHMENTS = "attachments"; + const QString ENTRY_AVATAR_ENTITIES = "avatarEntites"; const QString ENTRY_AVATAR_SCALE = "avatarScale"; const QString ENTRY_VERSION = "version"; diff --git a/interface/src/AvatarEntitiesBookmarks.cpp b/interface/src/AvatarEntitiesBookmarks.cpp deleted file mode 100644 index 21d3657346..0000000000 --- a/interface/src/AvatarEntitiesBookmarks.cpp +++ /dev/null @@ -1,168 +0,0 @@ -// -// AvatarEntitiesBookmarks.cpp -// interface/src -// -// Created by Dante Ruiz on 15/01/18. -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "MainWindow.h" -#include "Menu.h" -#include "AvatarEntitiesBookmarks.h" -#include "InterfaceLogging.h" - -#include "QVariantGLM.h" - -#include - -void addAvatarEntities(const QVariantList& avatarEntities) { - auto nodeList = DependencyManager::get(); - const QUuid myNodeID = nodeList->getSessionUUID(); - EntityTreePointer entityTree = DependencyManager::get()->getTree(); - if (!entityTree) { - return; - } - EntitySimulationPointer entitySimulation = entityTree->getSimulation(); - PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast(entitySimulation); - EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender(); - QScriptEngine scriptEngine; - for (int index = 0; index < avatarEntities.count(); index++) { - const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap(); - QVariant variantProperties = avatarEntityProperties["properties"]; - QVariantMap asMap = variantProperties.toMap(); - QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); - EntityItemProperties entityProperties; - EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties); - - entityProperties.setParentID(myNodeID); - entityProperties.setClientOnly(true); - entityProperties.setOwningAvatarID(myNodeID); - entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY); - entityProperties.markAllChanged(); - - EntityItemID id = EntityItemID(QUuid::createUuid()); - bool success = true; - entityTree->withWriteLock([&] { - EntityItemPointer entity = entityTree->addEntity(id, entityProperties); - if (entity) { - if (entityProperties.queryAACubeRelatedPropertyChanged()) { - // due to parenting, the server may not know where something is in world-space, so include the bounding cube. - bool success; - AACube queryAACube = entity->getQueryAACube(success); - if (success) { - entityProperties.setQueryAACube(queryAACube); - } - } - - entity->setLastBroadcast(usecTimestampNow()); - // since we're creating this object we will immediately volunteer to own its simulation - entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); - entityProperties.setLastEdited(entity->getLastEdited()); - } else { - qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree"; - success = false; - } - }); - - if (success) { - entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties); - } - } -} - -AvatarEntitiesBookmarks::AvatarEntitiesBookmarks() { - _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATAR_ENTITIES_BOOKMARKS_FILENAME; - Bookmarks::readFromFile(); -} - -void AvatarEntitiesBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { - auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatarEntities); - QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection); - _bookmarksMenu = menu->addMenu(MenuOption::AvatarEntitiesBookmarks); - _deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarEntitiesBookmark); - QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection); - - for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) { - addBookmarkToMenu(menubar, it.key(), it.value()); - } - - Bookmarks::sortActions(menubar, _bookmarksMenu); -} - -void AvatarEntitiesBookmarks::applyBookmarkedAvatarEntities() { - QAction* action = qobject_cast(sender()); - auto myAvatar = DependencyManager::get()->getMyAvatar(); - - const QMap bookmark = action->data().toMap(); - - if (bookmark.value(ENTRY_VERSION) == AVATAR_ENTITIES_BOOKMARK_VERSION) { - myAvatar->removeAvatarEntities(); - const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); - myAvatar->useFullAvatarURL(avatarUrl); - const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); - addAvatarEntities(avatarEntities); - const float& avatarScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); - myAvatar->setAvatarScale(avatarScale); - } else { - qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarEntitiesBookmark"; - } -} - -void AvatarEntitiesBookmarks::addBookmark() { - ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar Entities", "Name", QString()); - connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) { - disconnect(dlg, &ModalDialogListener::response, this, nullptr); - auto bookmarkName = response.toString(); - bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " "); - if (bookmarkName.length() == 0) { - return; - } - - auto myAvatar = DependencyManager::get()->getMyAvatar(); - - const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString(); - const QVariant& avatarScale = myAvatar->getAvatarScale(); - - QVariantMap bookmark; - bookmark.insert(ENTRY_VERSION, AVATAR_ENTITIES_BOOKMARK_VERSION); - bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); - bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale); - bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant()); - - Bookmarks::addBookmarkToFile(bookmarkName, bookmark); - }); -} - -void AvatarEntitiesBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) { - QAction* changeAction = _bookmarksMenu->newAction(); - changeAction->setData(bookmark); - connect(changeAction, SIGNAL(triggered()), this, SLOT(applyBookmarkedAvatarEntities())); - if (!_isMenuSorted) { - menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole); - } else { - // TODO: this is aggressive but other alternatives have proved less fruitful so far. - menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole); - Bookmarks::sortActions(menubar, _bookmarksMenu); - } -} diff --git a/interface/src/AvatarEntitiesBookmarks.h b/interface/src/AvatarEntitiesBookmarks.h deleted file mode 100644 index 0c70e4dbc0..0000000000 --- a/interface/src/AvatarEntitiesBookmarks.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// AvatarEntitiesBookmarks.h -// interface/src -// -// Created by Dante Ruiz on 15/01/18. -// Copyright 2018 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_AvatarEntitiesBookmarks_h -#define hifi_AvatarEntitiesBookmarks_h - -#include -#include "Bookmarks.h" - -class AvatarEntitiesBookmarks: public Bookmarks, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - AvatarEntitiesBookmarks(); - void setupMenus(Menu* menubar, MenuWrapper* menu) override; - -public slots: - void addBookmark(); - -protected: - void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override; - -private: - const QString AVATAR_ENTITIES_BOOKMARKS_FILENAME = "AvatarEntitiesBookmarks.json"; - const QString ENTRY_AVATAR_URL = "AvatarUrl"; - const QString ENTRY_AVATAR_SCALE = "AvatarScale"; - const QString ENTRY_AVATAR_ENTITIES = "AvatarEntities"; - const QString ENTRY_VERSION = "version"; - - const int AVATAR_ENTITIES_BOOKMARK_VERSION = 1; - -private slots: - void applyBookmarkedAvatarEntities(); -}; - -#endif diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 464de87fdb..40f7ad09ed 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -34,7 +34,6 @@ #include "audio/AudioScope.h" #include "avatar/AvatarManager.h" #include "AvatarBookmarks.h" -#include "AvatarEntitiesBookmarks.h" #include "devices/DdeFaceTracker.h" #include "MainWindow.h" #include "render/DrawStatus.h" @@ -207,10 +206,6 @@ Menu::Menu() { auto avatarBookmarks = DependencyManager::get(); avatarBookmarks->setupMenus(this, avatarMenu); - auto avatarEntitiesBookmarks = DependencyManager::get(); - avatarEntitiesBookmarks->setupMenus(this, avatarMenu); - - // Display menu ---------------------------------- // FIXME - this is not yet matching Alan's spec because it doesn't have // menus for "2D"/"3D" - we need to add support for detecting the appropriate diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 8cb1804fd4..24a4af9506 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -46,7 +46,6 @@ namespace MenuOption { const QString AutoMuteAudio = "Auto Mute Microphone"; const QString AvatarReceiveStats = "Show Receive Stats"; const QString AvatarBookmarks = "Avatar Bookmarks"; - const QString AvatarEntitiesBookmarks = "Avatar Entities Bookmarks"; const QString Back = "Back"; const QString BinaryEyelidControl = "Binary Eyelid Control"; const QString BookmarkAvatar = "Bookmark Avatar"; From a418129bea79b5f7cb2e0724305377d7db067258 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 23 Feb 2018 15:00:23 -0800 Subject: [PATCH 72/80] simplify to just changing timescales --- interface/src/avatar/MyAvatar.cpp | 44 +++++++++++-------------------- interface/src/avatar/MyAvatar.h | 4 +-- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d5d85b3365..4a58af9378 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1626,7 +1626,6 @@ controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action act void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; - glm::quat avatarOrientation = getWorldOrientation(); const float FLYING_MOTOR_TIMESCALE = 0.05f; const float WALKING_MOTOR_TIMESCALE = 0.2f; @@ -1654,10 +1653,11 @@ void MyAvatar::updateMotors() { // we decompose camera's rotation and store the twist part in motorRotation // however, we need to perform the decomposition in the avatar-frame // using the local UP axis and then transform back into world-frame - glm::quat headOrientation = glm::inverse(avatarOrientation) * getMyHead()->getHeadOrientation(); // avatar-frame + glm::quat orientation = getWorldOrientation(); + glm::quat headOrientation = glm::inverse(orientation) * getMyHead()->getHeadOrientation(); // avatar-frame glm::quat liftRotation; swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation); - motorRotation = avatarOrientation * motorRotation; + motorRotation = orientation * motorRotation; } if (_isPushing || _isBraking || !_isBeingPushed) { @@ -1669,35 +1669,21 @@ void MyAvatar::updateMotors() { } } if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { + if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { + motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); + } + else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { + motorRotation = getWorldOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); + } + else { + // world-frame + motorRotation = glm::quat(); + } if (_scriptedMotorMode == SCRIPTED_MOTOR_SIMPLE_MODE) { - if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { - motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); - } - else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { - motorRotation = avatarOrientation * glm::angleAxis(PI, Vectors::UNIT_Y); - } - else { - // world-frame - motorRotation = glm::quat(); - } _characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale); - } else { + } else { // dynamic mode - glm::vec3 avatarFrameVelocity; - if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { - // convert camera frame velocity to avatar frame - glm::quat cameraOrientation = qApp->getCamera().getOrientation(); - avatarFrameVelocity = glm::inverse(avatarOrientation) * cameraOrientation * _scriptedMotorVelocity; - } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_WORLD_FRAME) { - // convert world frame velocity to avatar frame - avatarFrameVelocity = glm::inverse(avatarOrientation) * _scriptedMotorVelocity; - } else { - // avatar frame - avatarFrameVelocity = _scriptedMotorVelocity; - } - // dynamic mode for scripted motor uses avatar frame and piggybacks off of the default action motor's timescales - motorRotation = avatarOrientation * glm::angleAxis(PI, Vectors::UNIT_Y); - _characterController.addMotor(avatarFrameVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); + _characterController.addMotor(_scriptedMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 464d94bce7..fa5206e128 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -69,7 +69,7 @@ class MyAvatar : public Avatar { * @property motorTimescale {float} Specifies how quickly the avatar should accelerate to meet the motorVelocity, * smaller values will result in higher acceleration. * @property motorReferenceFrame {string} Reference frame of the motorVelocity, must be one of the following: "avatar", "camera", "world" - * @property motorMode {string} Type of scripted motor behavior, "simple" = unmodified legacy behavior and "dynamic" = same as default motor + * @property motorMode {string} Type of scripted motor behavior, "simple" = use motorTimescale property (default mode) and "dynamic" = use action motor's timescales * @property collisionSoundURL {string} Specifies the sound to play when the avatar experiences a collision. * You can provide a mono or stereo 16-bit WAV file running at either 24 Khz or 48 Khz. * The latter is downsampled by the audio mixer, so all audio effectively plays back at a 24 Khz sample rate. @@ -668,7 +668,7 @@ private: void setScriptedMotorVelocity(const glm::vec3& velocity); void setScriptedMotorTimescale(float timescale); void setScriptedMotorFrame(QString frame); - void setScriptedMotorMode(QString frame); + void setScriptedMotorMode(QString mode); virtual void attach(const QString& modelURL, const QString& jointName = QString(), const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, bool isSoft = false, From da391564a8577676875c34335f5f31fcc742a1e9 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 23 Feb 2018 15:01:29 -0800 Subject: [PATCH 73/80] CR changes --- interface/src/avatar/MyAvatar.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4a58af9378..28cfb3e8f4 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1638,8 +1638,7 @@ void MyAvatar::updateMotors() { _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; - } - else { + } else { horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE; verticalMotorTimescale = INVALID_MOTOR_TIMESCALE; } @@ -1671,11 +1670,9 @@ void MyAvatar::updateMotors() { if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) { if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) { motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); - } - else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { + } else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) { motorRotation = getWorldOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y); - } - else { + } else { // world-frame motorRotation = glm::quat(); } From 283b5724ba42b69edec649ed15a0a60335c29e1d Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 23 Feb 2018 15:02:51 -0800 Subject: [PATCH 74/80] CR changes --- interface/src/avatar/MyAvatar.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 28cfb3e8f4..b6fa3fde96 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1799,10 +1799,10 @@ void MyAvatar::setScriptedMotorFrame(QString frame) { } } -void MyAvatar::setScriptedMotorMode(QString frame) { - if (frame.toLower() == "simple") { +void MyAvatar::setScriptedMotorMode(QString mode) { + if (mode.toLower() == "simple") { _scriptedMotorMode = SCRIPTED_MOTOR_SIMPLE_MODE; - } else if (frame.toLower() == "dynamic") { + } else if (mode.toLower() == "dynamic") { _scriptedMotorMode = SCRIPTED_MOTOR_DYNAMIC_MODE; } } From 800d15b4055d30076855735652af5f082cdd40db Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 23 Feb 2018 16:23:50 -0800 Subject: [PATCH 75/80] Make sure settings timer is running --- interface/src/Application.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e956195ca6..3dd75e2c25 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1512,6 +1512,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo settingsTimer->setSingleShot(false); settingsTimer->setInterval(SAVE_SETTINGS_INTERVAL); // 10s, Qt::CoarseTimer acceptable QObject::connect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings); + settingsTimer->start(); }, QThread::LowestPriority); if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { From 62f2ba0dadb6acf852966af445a1bd0f2ba04b2c Mon Sep 17 00:00:00 2001 From: vladest Date: Sat, 24 Feb 2018 09:32:08 +0100 Subject: [PATCH 76/80] More intellectual calculation of title positions taking in account actual sort indicator glyph size --- interface/resources/qml/controls-uit/Table.qml | 18 +++++++++++++++--- interface/resources/qml/hifi/Pal.qml | 15 +++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index c5a605c69e..3c1d0fcd3c 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -23,6 +23,9 @@ TableView { property bool expandSelectedRow: false property bool centerHeaderText: false readonly property real headerSpacing: 3 //spacing between sort indicator and table header title + property var titlePaintedPos: [] // storing extra data position behind painted + // title text and sort indicatorin table's header + signal titlePaintedPosSignal(int column) //signal that extradata position gets changed model: ListModel { } @@ -73,8 +76,10 @@ TableView { RalewayRegular { id: titleText - x: centerHeaderText ? parent.width/2 - - (paintedWidth/2 + (sortIndicatorVisible ? titleSort.paintedWidth/12 : 0)) : + x: centerHeaderText ? (parent.width - paintedWidth - + ((sortIndicatorVisible && + sortIndicatorColumn === styleData.column) ? + (titleSort.paintedWidth / 5 + tableView.headerSpacing) : 0)) / 2 : hifi.dimensions.tablePadding text: styleData.value size: hifi.fontSizes.tableHeading @@ -84,6 +89,8 @@ TableView { anchors.verticalCenter: parent.verticalCenter } + //actual image of sort indicator in glyph font only 20% of real font size + //i.e. if the charachter size set to 60 pixels, actual image is 12 pixels HiFiGlyphs { id: titleSort text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn @@ -92,8 +99,13 @@ TableView { size: hifi.fontSizes.tableHeadingIcon anchors.verticalCenter: titleText.verticalCenter anchors.left: titleText.right - anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 3 + tableView.headerSpacing) + anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 2.5) + tableView.headerSpacing visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column + onXChanged: { + titlePaintedPos[styleData.column] = titleText.x + titleText.paintedWidth + + paintedWidth / 5 + tableView.headerSpacing*2 + titlePaintedPosSignal(styleData.column) + } } Rectangle { diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index bb54ba9e57..8fb27714ee 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -30,7 +30,7 @@ Rectangle { property int myCardWidth: width - upperRightInfoContainer.width; property int myCardHeight: 100; property int rowHeight: 60; - property int actionButtonWidth: 55; + property int actionButtonWidth: 65; property int locationColumnWidth: 170; property int nearbyNameCardWidth: nearbyTable.width - (iAmAdmin ? (actionButtonWidth * 4) : (actionButtonWidth * 2)) - 4 - hifi.dimensions.scrollbarBackgroundWidth; property int connectionsNameCardWidth: connectionsTable.width - locationColumnWidth - actionButtonWidth - 4 - hifi.dimensions.scrollbarBackgroundWidth; @@ -415,6 +415,7 @@ Rectangle { movable: false; resizable: false; } + TableViewColumn { role: "ignore"; title: "IGNORE"; @@ -599,13 +600,23 @@ Rectangle { } // This Rectangle refers to the [?] popup button next to "NAMES" Rectangle { + id: questionRect color: hifi.colors.tableBackgroundLight; width: 20; height: hifi.dimensions.tableHeaderHeight - 2; anchors.left: nearbyTable.left; anchors.top: nearbyTable.top; anchors.topMargin: 1; - anchors.leftMargin: actionButtonWidth + nearbyNameCardWidth/2 + displayNameHeaderMetrics.width/2 + 6; + + Connections { + target: nearbyTable + onTitlePaintedPosSignal: { + if (column === 1) { // name column + questionRect.anchors.leftMargin = actionButtonWidth + nearbyTable.titlePaintedPos[column] + } + } + } + RalewayRegular { id: helpText; text: "[?]"; From 77ef0e05a1c3662cbd431b50b6b26bb22483a81f Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 26 Feb 2018 13:43:50 +0100 Subject: [PATCH 77/80] Search main window by checking object name --- libraries/ui/src/OffscreenUi.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 4309789b03..7a938f39c8 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -625,8 +625,7 @@ private: auto windows = qApp->topLevelWindows(); QWindow* result = nullptr; for (auto window : windows) { - QVariant isMainWindow = window->property("MainWindow"); - if (!qobject_cast(window)) { + if (window->objectName().contains("MainWindow")) { result = window; break; } From 0404b722e4c9766f8f7b6f7ca2e50cb03e40c83d Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 26 Feb 2018 10:00:32 -0800 Subject: [PATCH 78/80] Code review feedback --- .../render-utils/src/CauterizedModel.cpp | 2 +- .../render-utils/src/MeshPartPayload.cpp | 31 ++++++++++++------- libraries/render-utils/src/MeshPartPayload.h | 8 ++--- libraries/render-utils/src/Model.cpp | 9 ++---- libraries/shared/src/GLMHelpers.cpp | 2 +- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index fb1d31d273..6806b41647 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -275,7 +275,7 @@ void CauterizedModel::updateRenderItems() { data.setEnableCauterization(enableCauterization); data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, render::ItemKey::TAG_BITS_ALL); data.setLayer(isLayeredInFront, isLayeredInHUD); - data.setShapeKey(invalidatePayloadShapeKey, isWireframe); + data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning); }); } diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 99048fe324..2637d24d67 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -223,7 +223,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in assert(model && model->isLoaded()); - _useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); + bool useDualQuaternionSkinning = model->getUseDualQuaternionSkinning(); _blendedVertexBuffer = model->_blendedVertexBuffers[_meshIndex]; auto& modelMesh = model->getGeometry()->getMeshes().at(_meshIndex); @@ -231,7 +231,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in updateMeshPart(modelMesh, partIndex); - if (_useDualQuaternionSkinning) { + if (useDualQuaternionSkinning) { computeAdjustedLocalBound(state.clusterDualQuaternions); } else { computeAdjustedLocalBound(state.clusterMatrices); @@ -239,7 +239,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in updateTransform(transform, offsetTransform); Transform renderTransform = transform; - if (_useDualQuaternionSkinning) { + if (useDualQuaternionSkinning) { if (state.clusterDualQuaternions.size() == 1) { const auto& dq = state.clusterDualQuaternions[0]; Transform transform(dq.getRotation(), @@ -281,6 +281,13 @@ void ModelMeshPartPayload::notifyLocationChanged() { } void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices) { + + // reset cluster buffer if we change the cluster buffer type + if (_clusterBufferType != ClusterBufferType::Matrices) { + _clusterBuffer.reset(); + } + _clusterBufferType = ClusterBufferType::Matrices; + // Once computed the cluster matrices, update the buffer(s) if (clusterMatrices.size() > 1) { if (!_clusterBuffer) { @@ -295,6 +302,13 @@ void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clu } void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterDualQuaternions) { + + // reset cluster buffer if we change the cluster buffer type + if (_clusterBufferType != ClusterBufferType::DualQuaternions) { + _clusterBuffer.reset(); + } + _clusterBufferType = ClusterBufferType::DualQuaternions; + // Once computed the cluster matrices, update the buffer(s) if (clusterDualQuaternions.size() > 1) { if (!_clusterBuffer) { @@ -360,7 +374,7 @@ int ModelMeshPartPayload::getLayer() const { return _layer; } -void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe) { +void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe, bool useDualQuaternionSkinning) { if (invalidateShapeKey) { _shapeKey = ShapeKey::Builder::invalid(); return; @@ -407,7 +421,7 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe if (isWireframe) { builder.withWireframe(); } - if (_useDualQuaternionSkinning && isSkinned) { + if (isSkinned && useDualQuaternionSkinning) { builder.withDualQuatSkinned(); } @@ -497,10 +511,3 @@ void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterDualQuaternions); - void setUseDualQuaternionSkinning(bool value); - gpu::BufferPointer _clusterBuffer; + enum class ClusterBufferType { Matrices, DualQuaternions }; + ClusterBufferType _clusterBufferType { ClusterBufferType::Matrices }; + int _meshIndex; int _shapeID; bool _isSkinned{ false }; bool _isBlendShaped { false }; bool _hasTangents { false }; - bool _useDualQuaternionSkinning { false }; private: void initCache(const ModelPointer& model); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 8318715732..4198193fc9 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -288,7 +288,6 @@ void Model::updateRenderItems() { invalidatePayloadShapeKey, isWireframe, isVisible, viewTagBits, isLayeredInFront, isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { - data.setUseDualQuaternionSkinning(useDualQuaternionSkinning); if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); } else { @@ -314,7 +313,7 @@ void Model::updateRenderItems() { data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); - data.setShapeKey(invalidatePayloadShapeKey, isWireframe); + data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning); }); } @@ -1591,8 +1590,7 @@ void Model::addMaterial(graphics::MaterialLayer material, const std::string& par data.addMaterial(material); // if the material changed, we might need to update our item key or shape key data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits); - data.setUseDualQuaternionSkinning(useDualQuaternionSkinning); - data.setShapeKey(invalidatePayloadShapeKey, wireframe); + data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning); }); } } @@ -1618,8 +1616,7 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string data.removeMaterial(material); // if the material changed, we might need to update our item key or shape key data.updateKey(visible, layeredInFront || layeredInHUD, viewTagBits); - data.setUseDualQuaternionSkinning(useDualQuaternionSkinning); - data.setShapeKey(invalidatePayloadShapeKey, wireframe); + data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning); }); } } diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 46e484cf2b..ad2c991c49 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -602,5 +602,5 @@ glm::vec3 randVector() { } bool isNonUniformScale(const glm::vec3& scale) { - return fabsf(scale.x - scale.y) > EPSILON || fabsf(scale.y - scale.z) > EPSILON; + return fabsf(scale.x - scale.y) > EPSILON || fabsf(scale.y - scale.z) > EPSILON || fabsf(scale.z - scale.x); } From c8af9c6f04c85e2e29334b6f1ef92854e6466e6f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 26 Feb 2018 10:05:15 -0800 Subject: [PATCH 79/80] Missing EPSILON --- libraries/shared/src/GLMHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index ad2c991c49..72710a6a7d 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -602,5 +602,5 @@ glm::vec3 randVector() { } bool isNonUniformScale(const glm::vec3& scale) { - return fabsf(scale.x - scale.y) > EPSILON || fabsf(scale.y - scale.z) > EPSILON || fabsf(scale.z - scale.x); + return fabsf(scale.x - scale.y) > EPSILON || fabsf(scale.y - scale.z) > EPSILON || fabsf(scale.z - scale.x) > EPSILON; } From 2a204533f4344ccf4f116de169f6fd2ff75087ea Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 26 Feb 2018 15:56:11 -0800 Subject: [PATCH 80/80] Add comments to startup processes --- libraries/shared/src/LogHandler.cpp | 1 + libraries/shared/src/SettingInterface.cpp | 31 ++++++++++++--------- libraries/shared/src/SettingManager.h | 4 +-- libraries/shared/src/SharedUtil.cpp | 33 ++++++++++++++++++++--- libraries/shared/src/SharedUtil.h | 5 ++++ 5 files changed, 56 insertions(+), 18 deletions(-) diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index ff0d68dcce..cb3c0d07b2 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -192,6 +192,7 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont fprintf(stdout, "%s", qPrintable(logMessage)); #ifdef Q_OS_WIN + // On windows, this will output log lines into the Visual Studio "output" tab OutputDebugStringA(qPrintable(logMessage)); #endif return logMessage; diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 62f116795e..04da35656e 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -23,25 +23,27 @@ #include "SharedUtil.h" namespace Setting { - // cleans up the settings private instance. Should only be run once at closing down. - void cleanupPrivateInstance() { + // This should only run as a post-routine in the QCoreApplication destructor + void cleanupSettingsSaveThread() { auto globalManager = DependencyManager::get(); Q_ASSERT(qApp && globalManager); - // grab the thread before we nuke the instance + // Grab the settings thread to shut it down QThread* settingsManagerThread = globalManager->thread(); - // quit the settings manager thread + // Quit the settings manager thread and wait for it so we + // don't get concurrent accesses when we save all settings below settingsManagerThread->quit(); settingsManagerThread->wait(); - // Save all settings + // [IMPORTANT] Save all settings when the QApplication goes down globalManager->saveAll(); qCDebug(shared) << "Settings thread stopped."; } - void setupPrivateInstance() { + // This should only run as a pre-routine in the QCoreApplication constructor + void setupSettingsSaveThread() { auto globalManager = DependencyManager::get(); Q_ASSERT(qApp && globalManager); @@ -55,6 +57,9 @@ namespace Setting { QObject::connect(thread, &QThread::finished, globalManager.data(), &Manager::stopTimer); // Setup manager threading affinity + // This makes the timer fire on the settings thread so we don't block the main + // thread with a lot of file I/O. + // We bring back the manager to the main thread when the QApplication goes down globalManager->moveToThread(thread); QObject::connect(thread, &QThread::finished, globalManager.data(), [] { auto globalManager = DependencyManager::get(); @@ -63,15 +68,17 @@ namespace Setting { // Move manager back to the main thread (has to be done on owning thread) globalManager->moveToThread(qApp->thread()); }); - + + // Start the settings save thread thread->start(); qCDebug(shared) << "Settings thread started."; - // Register cleanupPrivateInstance to run inside QCoreApplication's destructor. - qAddPostRoutine(cleanupPrivateInstance); + // Register cleanupSettingsSaveThread to run inside QCoreApplication's destructor. + // This will cleanup the settings thread and save all settings before shut down. + qAddPostRoutine(cleanupSettingsSaveThread); } - // Sets up the settings private instance. Should only be run once at startup. preInit() must be run beforehand, + // Sets up the settings private instance. Should only be run once at startup. void init() { // Set settings format QSettings::setDefaultFormat(JSON_FORMAT); @@ -91,11 +98,11 @@ namespace Setting { qCDebug(shared) << (deleted ? "Deleted" : "Failed to delete") << "settings lock file" << settingsLockFilename; } - // Setup settings manager + // Setup settings manager, the manager will live until the process shuts down DependencyManager::set(); // Add pre-routine to setup threading - qAddPreRoutine(setupPrivateInstance); + qAddPreRoutine(setupSettingsSaveThread); } void Interface::init() { diff --git a/libraries/shared/src/SettingManager.h b/libraries/shared/src/SettingManager.h index ffdd4ba42a..6696a1ecf4 100644 --- a/libraries/shared/src/SettingManager.h +++ b/libraries/shared/src/SettingManager.h @@ -50,8 +50,8 @@ namespace Setting { QHash _pendingChanges; friend class Interface; - friend void cleanupPrivateInstance(); - friend void setupPrivateInstance(); + friend void cleanupSettingsSaveThread(); + friend void setupSettingsSaveThread(); }; } diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index d05e940322..412ea59dfe 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -66,10 +66,23 @@ extern "C" FILE * __cdecl __iob_func(void) { #include "OctalCode.h" #include "SharedLogging.h" +// Global instances are stored inside the QApplication properties +// to provide a single instance across DLL boundaries. +// This is something we cannot do here since several DLLs +// and our main binaries statically link this "shared" library +// resulting in multiple static memory blocks in different constexts +// But we need to be able to use global instances before the QApplication +// is setup, so to accomplish that we stage the global instances in a local +// map and setup a pre routine (commitGlobalInstances) that will run in the +// QApplication constructor and commit all the staged instances to the +// QApplication properties. +// Note: One of the side effects of this, is that no DLL loaded before +// the QApplication is constructed, can expect to access the existing staged +// global instanced. For this reason, we advise all DLLs be loaded after +// the QApplication is instanced. static std::mutex stagedGlobalInstancesMutex; static std::unordered_map stagedGlobalInstances; - std::mutex& globalInstancesMutex() { return stagedGlobalInstancesMutex; } @@ -82,6 +95,9 @@ static void commitGlobalInstances() { stagedGlobalInstances.clear(); } +// This call is necessary for global instances to work across DLL boundaries +// Ideally, this founction would be called at the top of the main function. +// See description at the top of the file. void setupGlobalInstances() { qAddPreRoutine(commitGlobalInstances); } @@ -819,8 +835,8 @@ bool similarStrings(const QString& stringA, const QString& stringB) { } void disableQtBearerPoll() { - // to disable the Qt constant wireless scanning, set the env for polling interval - qDebug() << "Disabling Qt wireless polling by using a negative value for QTimer::setInterval"; + // To disable the Qt constant wireless scanning, set the env for polling interval to -1 + // The constant polling causes ping spikes on windows every 10 seconds or so that affect the audio const QByteArray DISABLE_BEARER_POLL_TIMEOUT = QString::number(-1).toLocal8Bit(); qputenv("QT_BEARER_POLL_TIMEOUT", DISABLE_BEARER_POLL_TIMEOUT); } @@ -1185,19 +1201,28 @@ void watchParentProcess(int parentPID) { void setupHifiApplication(QString applicationName) { disableQtBearerPoll(); // Fixes wifi ping spikes + // Those calls are necessary to format the log correctly + // and to direct the application to the correct location + // for read/writes into AppData and other platform equivalents. QCoreApplication::setApplicationName(applicationName); QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); + // This ensures the global instances mechanism is correctly setup. + // You can find more details as to why this is important in the SharedUtil.h/cpp files setupGlobalInstances(); #ifndef WIN32 + // Windows tends to hold onto log lines until it has a sizeable buffer + // This makes the log feel unresponsive and trap useful log data in the log buffer + // when a crash occurs. + //Force windows to flush the buffer on each new line character to avoid this. setvbuf(stdout, NULL, _IOLBF, 0); #endif + // Install the standard hifi message handler so we get consistant log formatting qInstallMessageHandler(LogHandler::verboseMessageHandler); - qInfo() << "Starting."; } #ifdef Q_OS_WIN diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 7cb750d3e3..51a84bed06 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -36,7 +36,12 @@ std::unique_ptr& globalInstancePointer() { return instancePtr; } +// Sets up the global instances for use +// This NEEDS to be called on startup +// for any binary planing on using global instances +// More details in cpp file void setupGlobalInstances(); + std::mutex& globalInstancesMutex(); QVariant getGlobalInstance(const char* propertyName); void setGlobalInstance(const char* propertyName, const QVariant& variant);