diff --git a/.gitignore b/.gitignore index 072e6001da..4b6949e268 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,7 @@ gvr-interface/libs/* # ignore files for various dev environments TAGS *.sw[po] +*.qmlc # ignore node files for the console node_modules diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index fa9c73b12d..995a5bad27 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -41,8 +41,15 @@ EntityServer::EntityServer(ReceivedMessage& message) : DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics, PacketType::ChallengeOwnership }, - this, "handleEntityPacket"); + packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, + PacketType::EntityEdit, + PacketType::EntityErase, + PacketType::EntityPhysics, + PacketType::ChallengeOwnership, + PacketType::ChallengeOwnershipRequest, + PacketType::ChallengeOwnershipReply }, + this, + "handleEntityPacket"); connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification); _dynamicDomainVerificationTimer.setSingleShot(true); @@ -459,7 +466,7 @@ void EntityServer::startDynamicDomainVerification() { EntityItemPointer entity = tree->findEntityByEntityItemID(i.value()); if (entity) { - if (!entity->verifyStaticCertificateProperties()) { + if (!entity->getProperties().verifyStaticCertificateProperties()) { qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed" << "static certificate verification."; // Delete the entity if it doesn't pass static certificate verification diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 03014bae6a..11e4d533fb 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -175,7 +175,7 @@ bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filte return parentWasNew || ancestorsWereNew; } - // since we didn't have a parent niether of our parents or ancestors could be new additions + // since we didn't have a parent, neither of our parents or ancestors could be new additions return false; } @@ -204,7 +204,9 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) { +void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, + bool usesViewFrustum) { + DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum); // there are three types of traversal: // @@ -423,12 +425,19 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream uint64_t sendTime = usecTimestampNow(); auto nodeData = static_cast(params.nodeData); nodeData->stats.encodeStarted(); + auto entityNode = _node.toStrongRef(); + auto entityNodeData = static_cast(entityNode->getLinkedData()); while(!_sendQueue.empty()) { PrioritizedEntity queuedItem = _sendQueue.top(); EntityItemPointer entity = queuedItem.getEntity(); if (entity) { // Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again - if (entity->matchesJSONFilters(jsonFilters)) { + bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters); + if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entity->getID())) { + if (!jsonFilters.isEmpty() && entityMatchesFilters) { + // Record explicitly filtered-in entity so that extra entities can be flagged. + entityNodeData->insertSentFilteredEntity(entity->getID()); + } OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData); if (appendEntityState != OctreeElement::COMPLETED) { diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 49901491ff..a96a18494d 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -38,7 +38,8 @@ private: bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); - void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum); + void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, + bool usesViewFrustum); bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override; void preDistributionProcessing() override; diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 3f835678ac..bce6e7fe44 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -96,6 +96,14 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer _myServer->getOctree()->withWriteLock([&] { _myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode); }); + } else if (packetType == PacketType::ChallengeOwnershipRequest) { + _myServer->getOctree()->withWriteLock([&] { + _myServer->getOctree()->processChallengeOwnershipRequestPacket(*message, sendingNode); + }); + } else if (packetType == PacketType::ChallengeOwnershipReply) { + _myServer->getOctree()->withWriteLock([&] { + _myServer->getOctree()->processChallengeOwnershipReplyPacket(*message, sendingNode); + }); } else if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket); _receivedPacketCount++; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 89e3d403fc..3ae653307f 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -82,8 +82,12 @@ bool OctreeSendThread::process() { if (auto node = _node.lock()) { OctreeQueryNode* nodeData = static_cast(node->getLinkedData()); - // Sometimes the node data has not yet been linked, in which case we can't really do anything - if (nodeData && !nodeData->isShuttingDown()) { + // If we don't have the OctreeQueryNode at all + // or it's uninitialized because we haven't received a query yet from the client + // or we don't know where we should send packets for this node + // or we're shutting down + // then we can't send an entity data packet + if (nodeData && nodeData->hasReceivedFirstQuery() && node->getActiveSocket() && !nodeData->isShuttingDown()) { bool viewFrustumChanged = nodeData->updateCurrentViewFrustum(); packetDistributor(node, nodeData, viewFrustumChanged); } diff --git a/cmake/externals/hifiAudioCodec/CMakeLists.txt b/cmake/externals/hifiAudioCodec/CMakeLists.txt index a30396c6fd..e3ba36a440 100644 --- a/cmake/externals/hifiAudioCodec/CMakeLists.txt +++ b/cmake/externals/hifiAudioCodec/CMakeLists.txt @@ -5,43 +5,41 @@ set(EXTERNAL_NAME hifiAudioCodec) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) -if (NOT ANDROID) - - if (WIN32 OR APPLE) - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip - URL_MD5 23ec3fe51eaa155ea159a4971856fc13 - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD 1 - ) - else () - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux.zip - URL_MD5 7d37914a18aa4de971d2f45dd3043bde - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD 1 - ) - endif() - - # Hide this external target (for ide users) - set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") - - ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) - - set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) - - if (WIN32) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL) - elseif(APPLE) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) - elseif(NOT ANDROID) - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) - endif() - +if (WIN32) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-win-2.0.zip) + set(DOWNLOAD_MD5 9199d4dbd6b16bed736b235efe980e67) +elseif (APPLE) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-mac-2.0.zip) + set(DOWNLOAD_MD5 21649881e7d5dc94f922179be96f76ba) +elseif (ANDROID) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-android-2.0.zip) + set(DOWNLOAD_MD5 aef2a852600d498d58aa586668191683) +elseif (UNIX) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux-2.0.zip) + set(DOWNLOAD_MD5 67fb7755f9bcafb98a9fceea53bc7481) +else() + return() +endif() + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ${DOWNLOAD_URL} + URL_MD5 ${DOWNLOAD_MD5} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) + +if (WIN32) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL) +else() + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL) endif() diff --git a/cmake/externals/wasapi/CMakeLists.txt b/cmake/externals/wasapi/CMakeLists.txt index 4437024962..4c0ffaf88f 100644 --- a/cmake/externals/wasapi/CMakeLists.txt +++ b/cmake/externals/wasapi/CMakeLists.txt @@ -6,8 +6,8 @@ if (WIN32) include(ExternalProject) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip - URL_MD5 94f4765bdbcd53cd099f349ae031e769 + URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip + URL_MD5 4f40e49715a420fb67b45b9cee19052c CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/modules/FindOpenSSL.cmake b/cmake/modules/FindOpenSSL.cmake index 338dee7bc8..0619c4d587 100644 --- a/cmake/modules/FindOpenSSL.cmake +++ b/cmake/modules/FindOpenSSL.cmake @@ -60,7 +60,7 @@ if (WIN32 AND NOT CYGWIN) select_library_configurations(LIB_EAY) select_library_configurations(SSL_EAY) set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY}) - find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS}) + find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} NO_DEFAULT_PATH) endif() else() diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml index e1ba5499b6..0e48c1eff8 100644 --- a/domain-server/resources/web/content/index.shtml +++ b/domain-server/resources/web/content/index.shtml @@ -19,12 +19,13 @@ Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.
Note: Your domain's content will be replaced by the content you upload, but the backup files of your domain's content will not immediately be changed.

-

- If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:
-

C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz
-
/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz
-
/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz
-

+

If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:

+ +
C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz
+ +
/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz
+ +
/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz


diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 5358ad1adc..37c3c2adab 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -16,10 +16,10 @@ import Qt.labs.settings 1.0 import "../styles-uit" import "../controls-uit" as HifiControls -import "../windows" +import "../windows" as Windows import "../dialogs" -ScrollingWindow { +Windows.ScrollingWindow { id: root objectName: "AssetServer" title: "Asset Browser" diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index fcfff02b72..775bae7969 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -432,7 +432,8 @@ Item { anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter x: 240 onClicked: { - AddressManager.goToUser(thisNameCard.userName); + console.log("Vist user button clicked."); // Remove after debugging. + AddressManager.goToUser(thisNameCard.userName, false); UserActivityLogger.palAction("go_to_user", thisNameCard.userName); } } @@ -594,7 +595,10 @@ Item { // the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now. // FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script. // Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target. + // Position avatar 2 metres from the target in the direction that target avatar was facing. MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2})); - MyAvatar.orientation = Quat.multiply(avatar.orientation, {y: 1}); + + // Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch. + MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1})); } } diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 89f18e5ce2..efcf6ccfcf 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -827,7 +827,7 @@ Rectangle { hoverEnabled: enabled enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab" onClicked: { - AddressManager.goToUser(model.userName); + AddressManager.goToUser(model.userName, false); UserActivityLogger.palAction("go_to_user", model.userName); } onEntered: connectionsLocationData.color = hifi.colors.blueHighlight; diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 12c2ec1835..87ddce49ca 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -213,8 +213,8 @@ Rectangle { anchors.right: parent.right peak: model.peak; anchors.verticalCenter: parent.verticalCenter - visible: (bar.currentIndex === 1 && selectedHMD && isVR) || - (bar.currentIndex === 0 && selectedDesktop && !isVR) && + visible: ((bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR)) && Audio.devices.input.peakValuesAvailable; } } diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index aa1372494f..b6c29a1fad 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -33,6 +33,7 @@ Rectangle { property string dateOfPurchase: "--"; property bool isLightbox: false; property bool isMyCert: false; + property bool isCertificateInvalid: false; // Style color: hifi.colors.faintGray; Hifi.QmlCommerce { @@ -44,10 +45,11 @@ Rectangle { } else { root.marketplaceUrl = result.data.marketplace_item_url; root.isMyCert = result.isMyCert ? result.isMyCert : false; - 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.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); - root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000); + 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 (result.data.invalid_reason || result.data.transfer_status[0] === "failed") { @@ -65,6 +67,44 @@ Rectangle { } } } + + 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; + + popText.text = ""; + root.itemOwner = ""; + dateOfPurchaseHeader.text = ""; + root.dateOfPurchase = ""; + root.itemEdition = "Uncertified Copy"; + + 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; + 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; + } else { + console.log("Unknown certificate status received from ledger signal!"); + } + } } onCertificateIdChanged: { @@ -216,7 +256,7 @@ Rectangle { } AnonymousProRegular { id: isMyCertText; - visible: root.isMyCert; + visible: root.isMyCert && !root.isCertificateInvalid; text: "(Private)"; size: 18; // Anchors diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 1fe0dcc58b..fd7ce0fdfd 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -196,7 +196,38 @@ Item { anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; - anchors.rightMargin: 24; + + Item { + visible: transactionHistoryModel.count === 0 && root.historyReceived; + anchors.centerIn: parent; + width: parent.width - 12; + height: parent.height; + + HifiControlsUit.Separator { + colorScheme: 1; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + } + + RalewayRegular { + id: noActivityText; + text: "The Wallet app is in closed Beta.

To request entry and receive free HFC, please contact " + + "info@highfidelity.com with your High Fidelity account username and the email address registered to that account."; + // Text size + size: 24; + // Style + color: hifi.colors.blueAccent; + anchors.left: parent.left; + anchors.leftMargin: 12; + anchors.right: parent.right; + anchors.rightMargin: 12; + anchors.verticalCenter: parent.verticalCenter; + height: paintedHeight; + wrapMode: Text.WordWrap; + horizontalAlignment: Text.AlignHCenter; + } + } ListView { id: transactionHistory; @@ -294,17 +325,6 @@ Item { } } } - - // This should never be visible (since you immediately get 100 HFC) - FiraSansRegular { - id: emptyTransationHistory; - size: 24; - visible: !transactionHistory.visible && root.historyReceived; - text: "Recent Activity Unavailable"; - anchors.fill: parent; - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - } } } @@ -350,7 +370,9 @@ Item { } root.pendingCount = pendingCount; - transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"}); + if (pendingCount > 0) { + transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"}); + } } // diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 47d28486a9..3debc8b9e7 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -11,8 +11,11 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 as OriginalDialogs + import "../../styles-uit" import "../../controls-uit" +import "../dialogs" Rectangle { id: newModelDialog @@ -25,6 +28,15 @@ Rectangle { property bool punctuationMode: false property bool keyboardRasied: false + function errorMessageBox(message) { + return desktop.messageBox({ + icon: hifi.icons.warning, + defaultButton: OriginalDialogs.StandardButton.Ok, + title: "Error", + text: message + }); + } + Item { id: column1 anchors.rightMargin: 10 @@ -98,7 +110,6 @@ Rectangle { CheckBox { id: dynamic text: qsTr("Dynamic") - } Row { @@ -117,6 +128,7 @@ Rectangle { Text { id: text2 width: 160 + x: dynamic.width / 2 color: "#ffffff" text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors") wrapMode: Text.WordWrap @@ -139,15 +151,40 @@ Rectangle { ComboBox { id: collisionType + + property int priorIndex: 0 + property string staticMeshCollisionText: "Exact - All polygons" + property var collisionArray: ["No Collision", + "Basic - Whole model", + "Good - Sub-meshes", + staticMeshCollisionText, + "Box", + "Sphere"] + width: 200 z: 100 transformOrigin: Item.Center - model: ["No Collision", - "Basic - Whole model", - "Good - Sub-meshes", - "Exact - All polygons", - "Box", - "Sphere"] + model: collisionArray + + onCurrentIndexChanged: { + if (collisionArray[currentIndex] === staticMeshCollisionText) { + + if (dynamic.checked) { + currentIndex = priorIndex; + + errorMessageBox("Models with Automatic Collisions set to \"" + + staticMeshCollisionText + "\" cannot be dynamic."); + //--EARLY EXIT--( Can't have a static mesh model that's dynamic ) + return; + } + + dynamic.enabled = false; + } else { + dynamic.enabled = true; + } + + priorIndex = currentIndex; + } } Row { @@ -155,10 +192,10 @@ Rectangle { width: 200 height: 400 spacing: 5 - - anchors { - rightMargin: 15 - } + + anchors.horizontalCenter: column3.horizontalCenter + anchors.horizontalCenterOffset: -20 + Button { id: button1 text: qsTr("Add") diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4f051697ad..4b99d50761 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1825,6 +1825,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Preload Tablet sounds DependencyManager::get()->preloadSounds(); + _pendingIdleEvent = false; + _pendingRenderEvent = false; + qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID()); } @@ -2693,7 +2696,7 @@ bool Application::importFromZIP(const QString& filePath) { qDebug() << "A zip file has been dropped in: " << filePath; QUrl empty; // handle Blocks download from Marketplace - if (filePath.contains("vr.google.com/downloads")) { + if (filePath.contains("poly.google.com/downloads")) { addAssetToWorldFromURL(filePath); } else { qApp->getFileDownloadInterface()->runUnzip(filePath, empty, true, true, false); @@ -4441,7 +4444,7 @@ void Application::cameraModeChanged() { void Application::cameraMenuChanged() { auto menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::FullscreenMirror)) { - if (_myCamera.getMode() != CAMERA_MODE_MIRROR) { + if (!isHMDMode() && _myCamera.getMode() != CAMERA_MODE_MIRROR) { _myCamera.setMode(CAMERA_MODE_MIRROR); getMyAvatar()->reset(false, false, false); // to reset any active MyAvatar::FollowHelpers } @@ -6235,7 +6238,7 @@ void Application::addAssetToWorldFromURL(QString url) { if (url.contains("filename")) { filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL. } - if (url.contains("vr.google.com/downloads")) { + if (url.contains("poly.google.com/downloads")) { filename = url.section('/', -1); if (url.contains("noDownload")) { filename.remove(".zip?noDownload=false"); @@ -6270,7 +6273,7 @@ void Application::addAssetToWorldFromURLRequestFinished() { if (url.contains("filename")) { filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL. } - if (url.contains("vr.google.com/downloads")) { + if (url.contains("poly.google.com/downloads")) { filename = url.section('/', -1); if (url.contains("noDownload")) { filename.remove(".zip?noDownload=false"); @@ -7271,6 +7274,10 @@ void Application::updateDisplayMode() { menu->setIsOptionChecked(MenuOption::FirstPerson, true); cameraMenuChanged(); } + + // Remove the mirror camera option from menu if in HMD mode + auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror); + mirrorAction->setVisible(!isHmd); Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } diff --git a/interface/src/Application.h b/interface/src/Application.h index fbfb3979be..19a6dfdac1 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -708,7 +708,7 @@ private: friend class RenderEventHandler; - std::atomic _pendingIdleEvent { false }; - std::atomic _pendingRenderEvent { false }; + std::atomic _pendingIdleEvent { true }; + std::atomic _pendingRenderEvent { true }; }; #endif // hifi_Application_h diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index d3c8746e16..01ccbd0d9a 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -54,7 +54,9 @@ void LODManager::autoAdjustLOD(float batchTime, float engineRunTime, float delta float renderTime = batchTime + OVERLAY_AND_SWAP_TIME_BUDGET; float maxTime = glm::max(renderTime, engineRunTime); const float BLEND_TIMESCALE = 0.3f; // sec - float blend = BLEND_TIMESCALE / deltaTimeSec; + const float MIN_DELTA_TIME = 0.001f; + const float safeDeltaTime = glm::max(deltaTimeSec, MIN_DELTA_TIME); + float blend = BLEND_TIMESCALE / safeDeltaTime; if (blend > 1.0f) { blend = 1.0f; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0fceba82f0..0dfcf93a65 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -135,7 +135,7 @@ MyAvatar::MyAvatar(QThread* thread) : connect(&domainHandler, &DomainHandler::settingsReceived, this, &MyAvatar::restrictScaleFromDomainSettings); // when we leave a domain we lift whatever restrictions that domain may have placed on our scale - connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::clearScaleRestriction); + connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::leaveDomain); _bodySensorMatrix = deriveBodyFromHMDSensor(); @@ -2189,6 +2189,7 @@ void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) { setTargetScale(clampedTargetScale); qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); + emit(scaleChanged()); } float MyAvatar::getDomainMinScale() { @@ -2278,6 +2279,18 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings settings.endGroup(); } +void MyAvatar::leaveDomain() { + clearScaleRestriction(); + saveAvatarScale(); +} + +void MyAvatar::saveAvatarScale() { + Settings settings; + settings.beginGroup("Avatar"); + settings.setValue("scale", _targetScale); + settings.endGroup(); +} + void MyAvatar::clearScaleRestriction() { _domainMinimumScale = MIN_AVATAR_SCALE; _domainMaximumScale = MAX_AVATAR_SCALE; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 303a395a47..e4e8f8d02c 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -620,6 +620,10 @@ signals: void dominantHandChanged(const QString& hand); void sensorToWorldScaleChanged(float sensorToWorldScale); void attachmentsChanged(); + void scaleChanged(); + +private slots: + void leaveDomain(); private: @@ -637,6 +641,8 @@ private: virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual glm::vec3 getSkeletonPosition() const override; + void saveAvatarScale(); + glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; } float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; } QString getScriptedMotorFrame() const; diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index ae001010f0..42eb0ffc49 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -35,6 +35,14 @@ public: void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); void certificateInfo(const QString& certificateId); + enum CertificateStatus { + CERTIFICATE_STATUS_UNKNOWN = 0, + CERTIFICATE_STATUS_VERIFICATION_SUCCESS, + CERTIFICATE_STATUS_VERIFICATION_TIMEOUT, + CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED, + CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED, + }; + signals: void buyResult(QJsonObject result); void receiveAtResult(QJsonObject result); @@ -45,6 +53,8 @@ signals: void locationUpdateResult(QJsonObject result); void certificateInfoResult(QJsonObject result); + void updateCertificateStatus(const QString& certID, uint certStatus); + public slots: void buySuccess(QNetworkReply& reply); void buyFailure(QNetworkReply& reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 803264fa9f..f29e46d843 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -30,6 +30,12 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult); connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult); + connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus); + + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { + setPassphrase(""); + }); } void QmlCommerce::getWalletStatus() { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index ae63133425..d4f4aa35d2 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -45,6 +45,8 @@ signals: void accountResult(QJsonObject result); void certificateInfoResult(QJsonObject result); + void updateCertificateStatus(const QString& certID, uint certStatus); + protected: Q_INVOKABLE void getWalletStatus(); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index c7c09d8b03..85632ff8f1 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -319,6 +319,7 @@ Wallet::Wallet() { auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); + packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket"); connect(ledger.data(), &Ledger::accountResult, this, [&]() { auto wallet = DependencyManager::get(); @@ -717,50 +718,86 @@ bool Wallet::changePassphrase(const QString& newPassphrase) { } void Wallet::handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto nodeList = DependencyManager::get(); + + bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest; unsigned char decryptedText[64]; int certIDByteArraySize; int encryptedTextByteArraySize; + int challengingNodeUUIDByteArraySize; packet->readPrimitive(&certIDByteArraySize); packet->readPrimitive(&encryptedTextByteArraySize); + if (challengeOriginatedFromClient) { + packet->readPrimitive(&challengingNodeUUIDByteArraySize); + } QByteArray certID = packet->read(certIDByteArraySize); QByteArray encryptedText = packet->read(encryptedTextByteArraySize); + QByteArray challengingNodeUUID; + if (challengeOriginatedFromClient) { + challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize); + } RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); + int decryptionStatus = -1; if (rsa) { - const int decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize, + ERR_clear_error(); + decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize, reinterpret_cast(encryptedText.constData()), decryptedText, rsa, RSA_PKCS1_OAEP_PADDING); RSA_free(rsa); - - if (decryptionStatus != -1) { - auto nodeList = DependencyManager::get(); - - QByteArray decryptedTextByteArray = QByteArray(reinterpret_cast(decryptedText), decryptionStatus); - int decryptedTextByteArraySize = decryptedTextByteArray.size(); - int certIDSize = certID.size(); - // setup the packet - auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); - - decryptedTextPacket->writePrimitive(certIDSize); - decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); - decryptedTextPacket->write(certID); - decryptedTextPacket->write(decryptedTextByteArray); - - qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; - - nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); - } else { - qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; - } } else { qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed."; } + + QByteArray decryptedTextByteArray; + if (decryptionStatus > -1) { + decryptedTextByteArray = QByteArray(reinterpret_cast(decryptedText), decryptionStatus); + } + int decryptedTextByteArraySize = decryptedTextByteArray.size(); + int certIDSize = certID.size(); + // setup the packet + if (challengeOriginatedFromClient) { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, + certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), + true); + + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + decryptedTextPacket->write(challengingNodeUUID); + + qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; + + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + } else { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); + + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + + qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; + + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + } + + if (decryptionStatus == -1) { + qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; + long error = ERR_get_error(); + if (error != 0) { + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "RSA error:" << error_str; + } + } } void Wallet::account() { diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 75b43a251b..32dd74279b 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -25,7 +25,6 @@ LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& rende _distanceScaleEnd(distanceScaleEnd), _rayPickUID(DependencyManager::get()->createRayPick(rayProps)) { - for (auto& state : _renderStates) { if (!enabled || state.first != _currentRenderState) { @@ -119,23 +118,25 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); } glm::vec3 endVec; - if (((defaultState || !_lockEnd) && _objectLockEnd.first.isNull()) || type == IntersectionType::HUD) { + if (((defaultState || !_lockEnd) && _lockEndObject.id.isNull()) || type == IntersectionType::HUD) { endVec = pickRay.origin + pickRay.direction * distance; } else { - if (!_objectLockEnd.first.isNull()) { + if (!_lockEndObject.id.isNull()) { glm::vec3 pos; glm::quat rot; glm::vec3 dim; glm::vec3 registrationPoint; - if (_objectLockEnd.second) { - pos = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "position").value); - rot = quatFromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "rotation").value); - dim = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "dimensions").value); + if (_lockEndObject.isOverlay) { + pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value); + rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value); + dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value); registrationPoint = glm::vec3(0.5f); } else { - EntityItemProperties props = DependencyManager::get()->getEntityProperties(_objectLockEnd.first); - pos = props.getPosition(); - rot = props.getRotation(); + EntityItemProperties props = DependencyManager::get()->getEntityProperties(_lockEndObject.id); + glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); + glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; + pos = extractTranslation(finalPosAndRotMat); + rot = glmExtractRotation(finalPosAndRotMat); dim = props.getDimensions(); registrationPoint = props.getRegistrationPoint(); } @@ -209,7 +210,7 @@ void LaserPointer::update() { withReadLock([&] { RayPickResult prevRayPickResult = qApp->getRayPickManager().getPrevRayPickResult(_rayPickUID); if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && - (prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) { + (prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) { float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult.distance; updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, distance, prevRayPickResult.objectID, prevRayPickResult.searchRay, false); disableRenderState(_defaultRenderStates[_currentRenderState].second); @@ -233,9 +234,11 @@ void LaserPointer::setLaserLength(const float laserLength) { }); } -void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay) { +void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat) { withWriteLock([&] { - _objectLockEnd = std::pair(objectID, isOverlay); + _lockEndObject.id = objectID; + _lockEndObject.isOverlay = isOverlay; + _lockEndObject.offsetMat = offsetMat; }); } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index f2350c7199..896752a96e 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -21,6 +21,12 @@ class RayPickResult; +struct LockEndObject { + QUuid id { QUuid() }; + bool isOverlay { false }; + glm::mat4 offsetMat { glm::mat4() }; +}; + class RenderState { public: @@ -74,7 +80,7 @@ public: void setPrecisionPicking(const bool precisionPicking); void setLaserLength(const float laserLength); - void setLockEndUUID(QUuid objectID, const bool isOverlay); + void setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4()); void setIgnoreItems(const QVector& ignoreItems) const; void setIncludeItems(const QVector& includeItems) const; @@ -91,7 +97,7 @@ private: bool _centerEndY; bool _lockEnd; bool _distanceScaleEnd; - std::pair _objectLockEnd { std::pair(QUuid(), false)}; + LockEndObject _lockEndObject; const QUuid _rayPickUID; diff --git a/interface/src/raypick/LaserPointerManager.cpp b/interface/src/raypick/LaserPointerManager.cpp index 9d58cc2587..45420d1488 100644 --- a/interface/src/raypick/LaserPointerManager.cpp +++ b/interface/src/raypick/LaserPointerManager.cpp @@ -113,9 +113,9 @@ void LaserPointerManager::setIncludeItems(const QUuid& uid, const QVector } } -void LaserPointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const { +void LaserPointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) const { auto laserPointer = find(uid); if (laserPointer) { - laserPointer->setLockEndUUID(objectID, isOverlay); + laserPointer->setLockEndUUID(objectID, isOverlay, offsetMat); } } diff --git a/interface/src/raypick/LaserPointerManager.h b/interface/src/raypick/LaserPointerManager.h index e302318483..25089a291a 100644 --- a/interface/src/raypick/LaserPointerManager.h +++ b/interface/src/raypick/LaserPointerManager.h @@ -39,7 +39,7 @@ public: void setIgnoreItems(const QUuid& uid, const QVector& ignoreEntities) const; void setIncludeItems(const QUuid& uid, const QVector& includeEntities) const; - void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay) const; + void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const; void update(); diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index 19262e6e5d..986c53e24f 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -35,7 +35,7 @@ public slots: Q_INVOKABLE void setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreEntities) const; Q_INVOKABLE void setIncludeItems(const QUuid& uid, const QScriptValue& includeEntities) const; - Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay); } + Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay, offsetMat); } private: static RenderState buildRenderState(const QVariantMap& propMap); diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index 808396c901..1adf5650dd 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -71,6 +71,12 @@ bool SelectionScriptingInterface::removeFromSelectedItemsList(const QString& lis return false; } +bool SelectionScriptingInterface::clearSelectedItemsList(const QString& listName) { + _selectedItemsListMap.insert(listName, GameplayObjects()); + emit selectedItemsListChanged(listName); + return true; +} + template bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) { GameplayObjects currentList = _selectedItemsListMap.value(listName); currentList.addToGameplayObjects(idToAdd); diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index d1a372c5c4..d9003c2c32 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -56,11 +56,45 @@ public: GameplayObjects getList(const QString& listName); + /**jsdoc + * Prints out the list of avatars, entities and overlays stored in a particular selection. + * @function Selection.printList + * @param listName {string} name of the selection + */ Q_INVOKABLE void printList(const QString& listName); + /**jsdoc + * Removes a named selection from the list of selections. + * @function Selection.removeListFromMap + * @param listName {string} name of the selection + * @returns {bool} true if the selection existed and was successfully removed. + */ Q_INVOKABLE bool removeListFromMap(const QString& listName); + /**jsdoc + * Add an item in a selection. + * @function Selection.addToSelectedItemsList + * @param listName {string} name of the selection + * @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay") + * @param id {EntityID} the Id of the item to add to the selection + * @returns {bool} true if the item was successfully added. + */ Q_INVOKABLE bool addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id); + /**jsdoc + * Remove an item from a selection. + * @function Selection.removeFromSelectedItemsList + * @param listName {string} name of the selection + * @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay") + * @param id {EntityID} the Id of the item to remove + * @returns {bool} true if the item was successfully removed. + */ Q_INVOKABLE bool removeFromSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id); + /**jsdoc + * Remove all items from a selection. + * @function Selection.clearSelectedItemsList + * @param listName {string} name of the selection + * @returns {bool} true if the item was successfully cleared. + */ + Q_INVOKABLE bool clearSelectedItemsList(const QString& listName); signals: void selectedItemsListChanged(const QString& listName); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 42425932df..d157e29959 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -265,7 +265,7 @@ void Base3DOverlay::locationChanged(bool tellPhysics) { SpatiallyNestable::locationChanged(tellPhysics); // Force the actual update of the render transform through the notify call - notifyRenderTransformChange(); + notifyRenderVariableChange(); } void Base3DOverlay::parentDeleted() { @@ -275,18 +275,21 @@ void Base3DOverlay::parentDeleted() { void Base3DOverlay::update(float duration) { // In Base3DOverlay, if its location or bound changed, the renderTrasnformDirty flag is true. // then the correct transform used for rendering is computed in the update transaction and assigned. - if (_renderTransformDirty) { + if (_renderVariableDirty) { auto itemID = getRenderItemID(); if (render::Item::isValidID(itemID)) { // Capture the render transform value in game loop before auto latestTransform = evalRenderTransform(); - _renderTransformDirty = false; + bool latestVisible = getVisible(); + _renderVariableDirty = false; render::ScenePointer scene = qApp->getMain3DScene(); render::Transaction transaction; - transaction.updateItem(itemID, [latestTransform](Overlay& data) { + transaction.updateItem(itemID, [latestTransform, latestVisible](Overlay& data) { auto overlay3D = dynamic_cast(&data); if (overlay3D) { + // TODO: overlays need to communicate all relavent render properties through transactions overlay3D->setRenderTransform(latestTransform); + overlay3D->setRenderVisible(latestVisible); } }); scene->enqueueTransaction(transaction); @@ -294,8 +297,8 @@ void Base3DOverlay::update(float duration) { } } -void Base3DOverlay::notifyRenderTransformChange() const { - _renderTransformDirty = true; +void Base3DOverlay::notifyRenderVariableChange() const { + _renderVariableDirty = true; } Transform Base3DOverlay::evalRenderTransform() { @@ -306,8 +309,17 @@ void Base3DOverlay::setRenderTransform(const Transform& transform) { _renderTransform = transform; } +void Base3DOverlay::setRenderVisible(bool visible) { + _renderVisible = visible; +} + SpatialParentTree* Base3DOverlay::getParentTree() const { auto entityTreeRenderer = qApp->getEntities(); EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; return entityTree.get(); } + +void Base3DOverlay::setVisible(bool visible) { + Parent::setVisible(visible); + notifyRenderVariableChange(); +} \ No newline at end of file diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 436cfbf97b..83c5873260 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -18,11 +18,14 @@ class Base3DOverlay : public Overlay, public SpatiallyNestable { Q_OBJECT + using Parent = Overlay; public: Base3DOverlay(); Base3DOverlay(const Base3DOverlay* base3DOverlay); + void setVisible(bool visible) override; + virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); } void setOverlayID(OverlayID overlayID) override { setID(overlayID); } @@ -56,7 +59,7 @@ public: void update(float deltatime) override; - void notifyRenderTransformChange() const; + void notifyRenderVariableChange() const; void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; @@ -76,8 +79,10 @@ protected: virtual void parentDeleted() override; mutable Transform _renderTransform; + mutable bool _renderVisible; virtual Transform evalRenderTransform(); virtual void setRenderTransform(const Transform& transform); + void setRenderVisible(bool visible); const Transform& getRenderTransform() const { return _renderTransform; } float _lineWidth; @@ -87,7 +92,7 @@ protected: bool _drawInFront; bool _drawHUDLayer; bool _isGrabbable { false }; - mutable bool _renderTransformDirty{ true }; + mutable bool _renderVariableDirty { true }; QString _name; }; diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 536b2c764f..69ce331c99 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -59,7 +59,7 @@ Circle3DOverlay::~Circle3DOverlay() { } } void Circle3DOverlay::render(RenderArgs* args) { - if (!_visible) { + if (!_renderVisible) { return; // do nothing if we're not visible } @@ -259,7 +259,7 @@ void Circle3DOverlay::render(RenderArgs* args) { } const render::ShapeKey Circle3DOverlay::getShapeKey() { - auto builder = render::ShapeKey::Builder().withoutCullFace().withUnlit(); + auto builder = render::ShapeKey::Builder().withoutCullFace(); if (isTransparent()) { builder.withTranslucent(); } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 70b75a0b17..d40c0972e9 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -14,6 +14,10 @@ #include #include +#include +#include +#include +#include #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) @@ -34,14 +38,14 @@ ContextOverlayInterface::ContextOverlayInterface() { _tabletScriptingInterface = DependencyManager::get(); _selectionScriptingInterface = DependencyManager::get(); - _selectionToSceneHandler.initialize("contextOverlayHighlightList"); - _entityPropertyFlags += PROP_POSITION; _entityPropertyFlags += PROP_ROTATION; _entityPropertyFlags += PROP_MARKETPLACE_ID; _entityPropertyFlags += PROP_DIMENSIONS; _entityPropertyFlags += PROP_REGISTRATION_POINT; _entityPropertyFlags += PROP_CERTIFICATE_ID; + _entityPropertyFlags += PROP_CLIENT_ONLY; + _entityPropertyFlags += PROP_OWNING_AVATAR_ID; auto entityScriptingInterface = DependencyManager::get().data(); connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay); @@ -60,12 +64,31 @@ ContextOverlayInterface::ContextOverlayInterface() { } }); connect(entityScriptingInterface, &EntityScriptingInterface::deletingEntity, this, &ContextOverlayInterface::deletingEntity); - connect(&qApp->getOverlays(), &Overlays::mousePressOnOverlay, this, &ContextOverlayInterface::contextOverlays_mousePressOnOverlay); connect(&qApp->getOverlays(), &Overlays::hoverEnterOverlay, this, &ContextOverlayInterface::contextOverlays_hoverEnterOverlay); connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay); - connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged); + { + render::Transaction transaction; + initializeSelectionToSceneHandler(_selectionToSceneHandlers[0], "contextOverlayHighlightList", transaction); + for (auto i = 1; i < MAX_SELECTION_COUNT; i++) { + auto selectionName = QString("highlightList") + QString::number(i); + initializeSelectionToSceneHandler(_selectionToSceneHandlers[i], selectionName, transaction); + } + const render::ScenePointer& scene = qApp->getMain3DScene(); + scene->enqueueTransaction(transaction); + } + + auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); + packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket"); + _challengeOwnershipTimeoutTimer.setSingleShot(true); +} + +void ContextOverlayInterface::initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction) { + handler.initialize(selectionName); + connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &handler, &SelectionToSceneHandler::selectedItemsListChanged); + transaction.resetSelectionHighlight(selectionName.toStdString()); } static const uint32_t MOUSE_HW_ID = 0; @@ -260,6 +283,89 @@ void ContextOverlayInterface::openInspectionCertificate() { auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH); _hmdScriptingInterface->openTablet(); + + setLastInspectedEntity(_currentEntityWithContextOverlay); + + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + + auto nodeList = DependencyManager::get(); + + if (entityProperties.getClientOnly()) { + if (entityProperties.verifyStaticCertificateProperties()) { + 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); + + 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(); + + 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 encryptedText = DependencyManager::get()->getTree()->computeEncryptedNonce(certID, ownerKey); + QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); + + int certIDByteArraySize = certID.length(); + int encryptedTextByteArraySize = encryptedText.length(); + int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + encryptedTextByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); + challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(encryptedText); + 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 { + auto ledger = DependencyManager::get(); + _challengeOwnershipTimeoutTimer.stop(); + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); + qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; + } + } } } @@ -293,3 +399,39 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent()); } } + +void ContextOverlayInterface::startChallengeOwnershipTimer() { + auto ledger = DependencyManager::get(); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + + connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { + qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity; + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT)); + }); + + _challengeOwnershipTimeoutTimer.start(5000); +} + +void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto ledger = DependencyManager::get(); + + _challengeOwnershipTimeoutTimer.stop(); + + int certIDByteArraySize; + int decryptedTextByteArraySize; + + packet->readPrimitive(&certIDByteArraySize); + packet->readPrimitive(&decryptedTextByteArraySize); + + QString certID(packet->read(certIDByteArraySize)); + QString decryptedText(packet->read(decryptedTextByteArraySize)); + + EntityItemID id; + bool verificationSuccess = DependencyManager::get()->getTree()->verifyDecryptedNonce(certID, decryptedText, id); + + if (verificationSuccess) { + emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + } else { + emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); + } +} diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index b4d3ddc0c2..81e398e15d 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -47,10 +47,12 @@ class ContextOverlayInterface : public QObject, public Dependency { OverlayID _contextOverlayID { UNKNOWN_OVERLAY_ID }; std::shared_ptr _contextOverlay { nullptr }; public: + ContextOverlayInterface(); Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } + void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; } void setEnabled(bool enabled); bool getEnabled() { return _enabled; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } @@ -70,10 +72,19 @@ public slots: void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event); bool contextOverlayFilterPassed(const EntityItemID& entityItemID); +private slots: + void handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode); + private: + + enum { + MAX_SELECTION_COUNT = 16 + }; + bool _verboseLogging { true }; bool _enabled { true }; - QUuid _currentEntityWithContextOverlay{}; + EntityItemID _currentEntityWithContextOverlay{}; + EntityItemID _lastInspectedEntity{}; QString _entityMarketplaceID; bool _contextOverlayJustClicked { false }; @@ -85,8 +96,12 @@ private: void disableEntityHighlight(const EntityItemID& entityItemID); void deletingEntity(const EntityItemID& entityItemID); + void initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction); - SelectionToSceneHandler _selectionToSceneHandler; + SelectionToSceneHandler _selectionToSceneHandlers[MAX_SELECTION_COUNT]; + + Q_INVOKABLE void startChallengeOwnershipTimer(); + QTimer _challengeOwnershipTimeoutTimer; }; #endif // hifi_ContextOverlayInterface_h diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index b6df1dbc31..caa9a1b397 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -44,7 +44,7 @@ Cube3DOverlay::~Cube3DOverlay() { } void Cube3DOverlay::render(RenderArgs* args) { - if (!_visible) { + if (!_renderVisible) { return; // do nothing if we're not visible } diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index ca275368cb..6fd132b531 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -53,7 +53,7 @@ AABox Grid3DOverlay::getBounds() const { } void Grid3DOverlay::render(RenderArgs* args) { - if (!_visible) { + if (!_renderVisible) { return; // do nothing if we're not visible } diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 313d71fc40..a8ae293c83 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -60,7 +60,7 @@ void Image3DOverlay::update(float deltatime) { } void Image3DOverlay::render(RenderArgs* args) { - if (!_visible || !getParentVisible() || !_texture || !_texture->isLoaded()) { + if (!_renderVisible || !getParentVisible() || !_texture || !_texture->isLoaded()) { return; } diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index bdb35d4f49..5ef820b2e0 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -96,7 +96,7 @@ void Line3DOverlay::setEnd(const glm::vec3& end) { } else { _direction = glm::vec3(0.0f); } - notifyRenderTransformChange(); + notifyRenderVariableChange(); } void Line3DOverlay::setLocalEnd(const glm::vec3& localEnd) { @@ -123,7 +123,7 @@ AABox Line3DOverlay::getBounds() const { } void Line3DOverlay::render(RenderArgs* args) { - if (!_visible) { + if (!_renderVisible) { return; // do nothing if we're not visible } diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index ac3fe66ddc..a9f0bfe2f1 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -37,7 +37,7 @@ AABox Planar3DOverlay::getBounds() const { void Planar3DOverlay::setDimensions(const glm::vec2& value) { _dimensions = value; - notifyRenderTransformChange(); + notifyRenderVariableChange(); } void Planar3DOverlay::setProperties(const QVariantMap& properties) { diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index 47d47b76a5..f2e324206d 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -23,7 +23,6 @@ Rectangle3DOverlay::Rectangle3DOverlay() : for (size_t i = 0; i < _rectGeometryIds.size(); ++i) { _rectGeometryIds[i] = geometryCache->allocateID(); } - qDebug() << "Building rect3d overlay"; } Rectangle3DOverlay::Rectangle3DOverlay(const Rectangle3DOverlay* rectangle3DOverlay) : @@ -34,11 +33,9 @@ Rectangle3DOverlay::Rectangle3DOverlay(const Rectangle3DOverlay* rectangle3DOver for (size_t i = 0; i < _rectGeometryIds.size(); ++i) { _rectGeometryIds[i] = geometryCache->allocateID(); } - qDebug() << "Building rect3d overlay"; } Rectangle3DOverlay::~Rectangle3DOverlay() { - qDebug() << "Destryoing rect3d overlay"; auto geometryCache = DependencyManager::get(); if (geometryCache) { geometryCache->releaseID(_geometryCacheID); @@ -49,7 +46,7 @@ Rectangle3DOverlay::~Rectangle3DOverlay() { } void Rectangle3DOverlay::render(RenderArgs* args) { - if (!_visible) { + if (!_renderVisible) { return; // do nothing if we're not visible } @@ -58,18 +55,11 @@ void Rectangle3DOverlay::render(RenderArgs* args) { const float MAX_COLOR = 255.0f; glm::vec4 rectangleColor(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); - glm::vec3 position = getPosition(); - glm::vec2 dimensions = getDimensions(); - glm::vec2 halfDimensions = dimensions * 0.5f; - glm::quat rotation = getRotation(); - auto batch = args->_batch; - if (batch) { - // FIXME Start using the _renderTransform instead of calling for Transform and Dimensions from here, do the custom things needed in evalRenderTransform() - Transform transform; - transform.setTranslation(position); - transform.setRotation(rotation); + Transform transform = getRenderTransform(); + glm::vec2 halfDimensions = transform.getScale() * 0.5f; + transform.setScale(1.0f); batch->setModelTransform(transform); auto geometryCache = DependencyManager::get(); @@ -124,8 +114,3 @@ void Rectangle3DOverlay::setProperties(const QVariantMap& properties) { Rectangle3DOverlay* Rectangle3DOverlay::createClone() const { return new Rectangle3DOverlay(this); } - -Transform Rectangle3DOverlay::evalRenderTransform() { - return getTransform(); -} - diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.h b/interface/src/ui/overlays/Rectangle3DOverlay.h index a26ed524fc..645553ed38 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.h +++ b/interface/src/ui/overlays/Rectangle3DOverlay.h @@ -29,9 +29,6 @@ public: virtual Rectangle3DOverlay* createClone() const override; -protected: - Transform evalRenderTransform() override; - private: int _geometryCacheID; std::array _rectGeometryIds; diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index a3b51d40bf..bd8e6a9728 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -24,7 +24,7 @@ Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay) : } void Shape3DOverlay::render(RenderArgs* args) { - if (!_visible) { + if (!_renderVisible) { return; // do nothing if we're not visible } diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index c9fc25b252..2013e5689a 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -27,7 +27,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : } void Sphere3DOverlay::render(RenderArgs* args) { - if (!_visible) { + if (!_renderVisible) { return; // do nothing if we're not visible } diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index d8188cf6dc..1cbc73304b 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -92,7 +92,7 @@ void Text3DOverlay::update(float deltatime) { } void Text3DOverlay::render(RenderArgs* args) { - if (!_visible || !getParentVisible()) { + if (!_renderVisible || !getParentVisible()) { return; // do nothing if we're not visible } diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index 8aa8490937..e5a418cce5 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -28,7 +28,7 @@ AABox Volume3DOverlay::getBounds() const { void Volume3DOverlay::setDimensions(const glm::vec3& value) { _localBoundingBox.setBox(-value / 2.0f, value); - notifyRenderTransformChange(); + notifyRenderVariableChange(); } void Volume3DOverlay::setProperties(const QVariantMap& properties) { diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 42d0ea0f12..d418a79fbf 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -268,7 +268,7 @@ Q_INVOKABLE int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) { } void Web3DOverlay::render(RenderArgs* args) { - if (!_visible || !getParentVisible()) { + if (!_renderVisible || !getParentVisible()) { return; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0f2bce5ca4..78aa1f4ba8 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -39,7 +39,12 @@ static std::mutex rigRegistryMutex; static bool isEqual(const glm::vec3& u, const glm::vec3& v) { const float EPSILON = 0.0001f; - return glm::length(u - v) / glm::length(u) <= EPSILON; + float uLen = glm::length(u); + if (uLen == 0.0f) { + return glm::length(v) <= EPSILON; + } else { + return (glm::length(u - v) / uLen) <= EPSILON; + } } static bool isEqual(const glm::quat& p, const glm::quat& q) { @@ -152,6 +157,7 @@ void Rig::overrideRoleAnimation(const QString& role, const QString& url, float f const float REFERENCE_FRAMES_PER_SECOND = 30.0f; float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; auto clipNode = std::make_shared(role, url, firstFrame, lastFrame, timeScale, loop, false); + _roleAnimStates[role] = { role, url, fps, loop, firstFrame, lastFrame }; AnimNode::Pointer parent = node->getParent(); parent->replaceChild(node, clipNode); } else { @@ -173,6 +179,11 @@ void Rig::restoreRoleAnimation(const QString& role) { } else { qCWarning(animation) << "Rig::restoreRoleAnimation could not find role " << role; } + + auto statesIter = _roleAnimStates.find(role); + if (statesIter != _roleAnimStates.end()) { + _roleAnimStates.erase(statesIter); + } } } else { qCWarning(animation) << "Rig::overrideRoleAnimation avatar not ready yet"; @@ -302,7 +313,9 @@ void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { _rigToGeometryTransform = glm::inverse(_geometryToRigTransform); // rebuild cached default poses - buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); + if (_animSkeleton) { + buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); + } } } @@ -1638,6 +1651,11 @@ void Rig::initAnimGraph(const QUrl& url) { _userAnimState = { UserAnimState::None, "", 30.0f, false, 0.0f, 0.0f }; overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame); } + // restore the role animations we had before reset. + for (auto& roleAnimState : _roleAnimStates) { + auto roleState = roleAnimState.second; + overrideRoleAnimation(roleState.role, roleState.url, roleState.fps, roleState.loop, roleState.firstFrame, roleState.lastFrame); + } _animLoading = false; emit onLoadComplete(); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 18d49c5f1e..e9cc444bd4 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -335,8 +335,22 @@ protected: float firstFrame; float lastFrame; }; + + struct RoleAnimState { + RoleAnimState() {} + RoleAnimState(const QString& roleId, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) : + role(roleId), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {} + + QString role; + QString url; + float fps; + bool loop; + float firstFrame; + float lastFrame; + }; UserAnimState _userAnimState; + std::map _roleAnimStates; float _leftHandOverlayAlpha { 0.0f }; float _rightHandOverlayAlpha { 0.0f }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 142e57c9e5..49d2431098 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -453,7 +453,9 @@ void Avatar::applyPositionDelta(const glm::vec3& delta) { void Avatar::measureMotionDerivatives(float deltaTime) { PerformanceTimer perfTimer("derivatives"); // linear - float invDeltaTime = 1.0f / deltaTime; + const float MIN_DELTA_TIME = 0.001f; + const float safeDeltaTime = glm::max(deltaTime, MIN_DELTA_TIME); + float invDeltaTime = 1.0f / safeDeltaTime; // Floating point error prevents us from computing velocity in a naive way // (e.g. vel = (pos - oldPos) / dt) so instead we use _positionOffsetAccumulator. glm::vec3 velocity = _positionDeltaAccumulator * invDeltaTime; @@ -728,19 +730,19 @@ void Avatar::simulateAttachments(float deltaTime) { glm::quat jointRotation; if (attachment.isSoft) { // soft attachments do not have transform offsets - model->setTranslation(getPosition()); - model->setRotation(getOrientation() * Quaternions::Y_180); + model->setTransformNoUpdateRenderItems(Transform(getOrientation() * Quaternions::Y_180, glm::vec3(1.0), getPosition())); model->simulate(deltaTime); + model->updateRenderItems(); } else { if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPosition) && _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRotation)) { - model->setTranslation(jointPosition + jointRotation * attachment.translation * getModelScale()); - model->setRotation(jointRotation * attachment.rotation); + model->setTransformNoUpdateRenderItems(Transform(jointRotation * attachment.rotation, glm::vec3(1.0), jointPosition + jointRotation * attachment.translation * getModelScale())); float scale = getModelScale() * attachment.scale; model->setScaleToFit(true, model->getNaturalDimensions() * scale, true); // hack to force rescale model->setSnapModelToCenter(false); // hack to force resnap model->setSnapModelToCenter(true); model->simulate(deltaTime); + model->updateRenderItems(); } } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 877bcd9353..bd313ac133 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2223,11 +2223,13 @@ glm::vec3 variantToVec3(const QVariant& var) { return result; } -void AttachmentData::fromVariant(const QVariant& variant) { +bool AttachmentData::fromVariant(const QVariant& variant) { + bool isValid = false; auto map = variant.toMap(); if (map.contains("modelUrl")) { auto urlString = map["modelUrl"].toString(); modelURL = urlString; + isValid = true; } if (map.contains("jointName")) { jointName = map["jointName"].toString(); @@ -2244,6 +2246,7 @@ void AttachmentData::fromVariant(const QVariant& variant) { if (map.contains("soft")) { isSoft = map["soft"].toBool(); } + return isValid; } QVariantList AvatarData::getAttachmentsVariant() const { @@ -2259,8 +2262,7 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) { newAttachments.reserve(variant.size()); for (const auto& attachmentVar : variant) { AttachmentData attachment; - attachment.fromVariant(attachmentVar); - if (!attachment.modelURL.isEmpty()) { + if (attachment.fromVariant(attachmentVar)) { newAttachments.append(attachment); } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 89fe270af1..4dabbb3ff5 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -15,21 +15,7 @@ #include #include #include - -/* VS2010 defines stdint.h, but not inttypes.h */ -#if defined(_MSC_VER) -typedef signed char int8_t; -typedef signed short int16_t; -typedef signed int int32_t; -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef signed long long int64_t; -typedef unsigned long long quint64; -#define PRId64 "I64d" -#else #include -#endif #include #include @@ -903,7 +889,7 @@ public: void fromJson(const QJsonObject& json); QVariant toVariant() const; - void fromVariant(const QVariant& variant); + bool fromVariant(const QVariant& variant); }; QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 0235f1b7a3..7c96f00ede 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -342,23 +342,23 @@ void ZoneEntityRenderer::updateHazeFromEntity(const TypedEntityPointer& entity) haze->setHazeActive(hazeMode == COMPONENT_MODE_ENABLED); haze->setAltitudeBased(_hazeProperties.getHazeAltitudeEffect()); - haze->setHazeRangeFactor(model::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeRange())); + haze->setHazeRangeFactor(model::Haze::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeRange())); xColor hazeColor = _hazeProperties.getHazeColor(); haze->setHazeColor(glm::vec3(hazeColor.red / 255.0, hazeColor.green / 255.0, hazeColor.blue / 255.0)); xColor hazeGlareColor = _hazeProperties.getHazeGlareColor(); - haze->setDirectionalLightColor(glm::vec3(hazeGlareColor.red / 255.0, hazeGlareColor.green / 255.0, hazeGlareColor.blue / 255.0)); + haze->setHazeGlareColor(glm::vec3(hazeGlareColor.red / 255.0, hazeGlareColor.green / 255.0, hazeGlareColor.blue / 255.0)); haze->setHazeEnableGlare(_hazeProperties.getHazeEnableGlare()); - haze->setDirectionalLightBlend(model::convertDirectionalLightAngleToPower(_hazeProperties.getHazeGlareAngle())); + haze->setHazeGlareBlend(model::Haze::convertGlareAngleToPower(_hazeProperties.getHazeGlareAngle())); float hazeAltitude = _hazeProperties.getHazeCeiling() - _hazeProperties.getHazeBaseRef(); - haze->setHazeAltitudeFactor(model::convertHazeAltitudeToHazeAltitudeFactor(hazeAltitude)); + haze->setHazeAltitudeFactor(model::Haze::convertHazeAltitudeToHazeAltitudeFactor(hazeAltitude)); haze->setHazeBaseReference(_hazeProperties.getHazeBaseRef()); - haze->setHazeBackgroundBlendValue(_hazeProperties.getHazeBackgroundBlend()); + haze->setHazeBackgroundBlend(_hazeProperties.getHazeBackgroundBlend()); haze->setHazeAttenuateKeyLight(_hazeProperties.getHazeAttenuateKeyLight()); - haze->setHazeKeyLightRangeFactor(model::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeKeyLightRange())); - haze->setHazeKeyLightAltitudeFactor(model::convertHazeAltitudeToHazeAltitudeFactor(_hazeProperties.getHazeKeyLightAltitude())); + haze->setHazeKeyLightRangeFactor(model::Haze::convertHazeRangeToHazeRangeFactor(_hazeProperties.getHazeKeyLightRange())); + haze->setHazeKeyLightAltitudeFactor(model::Haze::convertHazeAltitudeToHazeAltitudeFactor(_hazeProperties.getHazeKeyLightAltitude())); haze->setZoneTransform(entity->getTransform().getMatrix()); } diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index 322d69da16..c23740654e 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -1,4 +1,4 @@ set(TARGET_NAME entities) setup_hifi_library(Network Script) include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") -link_hifi_libraries(shared networking octree avatars) +link_hifi_libraries(shared networking octree avatars model) diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp index 2f9423daa3..764c420197 100644 --- a/libraries/entities/src/DiffTraversal.cpp +++ b/libraries/entities/src/DiffTraversal.cpp @@ -142,7 +142,8 @@ DiffTraversal::DiffTraversal() { _path.reserve(MIN_PATH_DEPTH); } -DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) { +DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, + int32_t lodLevelOffset, bool usesViewFrustum) { assert(root); // there are three types of traversal: // diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h index c26e48ae5f..eb7168356e 100644 --- a/libraries/entities/src/DiffTraversal.h +++ b/libraries/entities/src/DiffTraversal.h @@ -57,7 +57,8 @@ public: DiffTraversal(); - Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum); + Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, + bool usesViewFrustum); const ViewFrustum& getCurrentView() const { return _currentView.viewFrustum; } const ViewFrustum& getCompletedView() const { return _completedView.viewFrustum; } diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index e82ed82093..168b0cd446 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -93,27 +93,41 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); - bool success; + OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send auto nodeList = DependencyManager::get(); + + EntityPropertyFlags didntFitProperties; + EntityItemProperties propertiesCopy = properties; + if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) { - EntityItemProperties propertiesCopy = properties; const QUuid myNodeID = nodeList->getSessionUUID(); propertiesCopy.setParentID(myNodeID); - success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut); - } else { - success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut); } - if (success) { - #ifdef WANT_DEBUG - qCDebug(entities) << "calling queueOctreeEditMessage()..."; - qCDebug(entities) << " id:" << entityItemID; - qCDebug(entities) << " properties:" << properties; - #endif - queueOctreeEditMessage(type, bufferOut); - if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { - emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get()->getPlaceName()); + EntityPropertyFlags requestedProperties = propertiesCopy.getChangedProperties(); + + while (encodeResult == OctreeElement::PARTIAL) { + encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties); + + if (encodeResult != OctreeElement::NONE) { + #ifdef WANT_DEBUG + qCDebug(entities) << "calling queueOctreeEditMessage()..."; + qCDebug(entities) << " id:" << entityItemID; + qCDebug(entities) << " properties:" << properties; + #endif + queueOctreeEditMessage(type, bufferOut); + if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { + emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get()->getPlaceName()); + } } + + // if we still have properties to send, switch the message type to edit, and request only the packets that didn't fit + if (encodeResult != OctreeElement::COMPLETED) { + type = PacketType::EntityEdit; + requestedProperties = didntFitProperties; + } + + bufferOut.resize(NLPacket::maxPayloadSize(type)); // resize our output buffer for the next packet } } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 4f5db991c8..3f054e1ccb 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -14,10 +14,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -87,7 +83,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_ANGULAR_VELOCITY; requestedProperties += PROP_ACCELERATION; - requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete + requestedProperties += PROP_DIMENSIONS; requestedProperties += PROP_DENSITY; requestedProperties += PROP_GRAVITY; requestedProperties += PROP_DAMPING; @@ -245,7 +241,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); @@ -1575,116 +1571,6 @@ float EntityItem::getRadius() const { return 0.5f * glm::length(getDimensions()); } -// Checking Certifiable Properties -#define ADD_STRING_PROPERTY(n, N) if (!propertySet.get##N().isEmpty()) json[#n] = propertySet.get##N() -#define ADD_ENUM_PROPERTY(n, N) json[#n] = propertySet.get##N##AsString() -#define ADD_INT_PROPERTY(n, N) if (propertySet.get##N() != 0) json[#n] = (propertySet.get##N() == (quint32) -1) ? -1.0 : ((double) propertySet.get##N()) -QByteArray EntityItem::getStaticCertificateJSON() const { - // Produce a compact json of every non-default static certificate property, with the property names in alphabetical order. - // The static certificate properties include all an only those properties that cannot be changed without altering the identity - // of the entity as reviewed during the certification submission. - - QJsonObject json; - EntityItemProperties propertySet = getProperties(); // Note: neither EntityItem nor EntityitemProperties "properties" are QObject "properties"! - // It is important that this be reproducible in the same order each time. Since we also generate these on the server, we do it alphabetically - // to help maintainence in two different code bases. - if (!propertySet.getAnimation().getURL().isEmpty()) { - json["animationURL"] = propertySet.getAnimation().getURL(); - } - ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); - ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); - ADD_INT_PROPERTY(editionNumber, EditionNumber); - ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber); - ADD_STRING_PROPERTY(itemArtist, ItemArtist); - ADD_STRING_PROPERTY(itemCategories, ItemCategories); - ADD_STRING_PROPERTY(itemDescription, ItemDescription); - ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense); - ADD_STRING_PROPERTY(itemName, ItemName); - ADD_INT_PROPERTY(limitedRun, LimitedRun); - ADD_STRING_PROPERTY(marketplaceID, MarketplaceID); - ADD_STRING_PROPERTY(modelURL, ModelURL); - ADD_STRING_PROPERTY(script, Script); - ADD_ENUM_PROPERTY(shapeType, ShapeType); - json["type"] = EntityTypes::getEntityTypeName(propertySet.getType()); - - return QJsonDocument(json).toJson(QJsonDocument::Compact); -} -QByteArray EntityItem::getStaticCertificateHash() const { - return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256); -} - -bool EntityItem::verifyStaticCertificateProperties() { - // True IIF a non-empty certificateID matches the static certificate json. - // I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash. - - if (getCertificateID().isEmpty()) { - return false; - } - - const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8(); - const unsigned char* marketplacePublicKey = reinterpret_cast(marketplacePublicKeyByteArray.constData()); - int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length(); - - BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength); - EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); - if (evp_key) { - RSA* rsa = EVP_PKEY_get1_RSA(evp_key); - if (rsa) { - const QByteArray digestByteArray = getStaticCertificateHash(); - const unsigned char* digest = reinterpret_cast(digestByteArray.constData()); - int digestLength = digestByteArray.length(); - - const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8()); - const unsigned char* signature = reinterpret_cast(signatureByteArray.constData()); - int signatureLength = signatureByteArray.length(); - - ERR_clear_error(); - bool answer = RSA_verify(NID_sha256, - digest, - digestLength, - signature, - signatureLength, - rsa); - long error = ERR_get_error(); - if (error != 0) { - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str - << "\nStatic Cert JSON:" << getStaticCertificateJSON() - << "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength - << "\nDigest:" << digest << "\nDigest Length:" << digestLength - << "\nSignature:" << signature << "\nSignature Length:" << signatureLength; - } - RSA_free(rsa); - if (bio) { - BIO_free(bio); - } - if (evp_key) { - EVP_PKEY_free(evp_key); - } - return answer; - } else { - if (bio) { - BIO_free(bio); - } - if (evp_key) { - EVP_PKEY_free(evp_key); - } - long error = ERR_get_error(); - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; - return false; - } - } else { - if (bio) { - BIO_free(bio); - } - long error = ERR_get_error(); - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; - return false; - } -} - void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const { if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) { glm::mat4 scale = glm::scale(getDimensions()); @@ -1769,7 +1655,6 @@ void EntityItem::updateDimensions(const glm::vec3& value) { setDimensions(value); markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); _queryAACubeSet = false; - dimensionsChanged(); } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 79862b9bd2..76a5205960 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -328,9 +328,6 @@ public: void setEntityInstanceNumber(const quint32&); QString getCertificateID() const; void setCertificateID(const QString& value); - QByteArray getStaticCertificateJSON() const; - QByteArray getStaticCertificateHash() const; - bool verifyStaticCertificateProperties(); // TODO: get rid of users of getRadius()... float getRadius() const; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3bbd6ce8e6..108fc14e30 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -14,6 +14,15 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -1212,8 +1221,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue // // TODO: Implement support for script and visible properties. // -bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, - QByteArray& buffer) { +OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, + QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties) { + OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too. OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro @@ -1255,17 +1265,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem QByteArray encodedUpdateDelta = updateDeltaCoder; EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); - EntityPropertyFlags requestedProperties = properties.getChangedProperties(); EntityPropertyFlags propertiesDidntFit = requestedProperties; - // TODO: we need to handle the multi-pass form of this, similar to how we handle entity data - // - // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, - // then our modelTreeElementExtraEncodeData should include data about which properties we need to append. - //if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) { - // requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID()); - //} - LevelDetails entityLevel = packetData->startLevel(); // Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this @@ -1293,7 +1294,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem int propertyCount = 0; bool headerFits = successIDFits && successTypeFits && successLastEditedFits - && successLastUpdatedFits && successPropertyFlagsFits; + && successLastUpdatedFits && successPropertyFlagsFits; int startOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -1307,7 +1308,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray()); APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity()); @@ -1463,6 +1464,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem properties.getType() == EntityTypes::Sphere) { APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } + APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); @@ -1513,12 +1515,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem // If any part of the model items didn't fit, then the element is considered partial if (appendState != OctreeElement::COMPLETED) { - // TODO: handle mechanism for handling partial fitting data! - // add this item into our list for the next appendElementData() pass - //modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit); - - // for now, if it's not complete, it's not successful - success = false; + didntFitProperties = propertiesDidntFit; } } @@ -1534,11 +1531,15 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem } else { qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer."; success = false; + appendState = OctreeElement::NONE; // if we got here, then we didn't include the item + // maybe we should assert!!! } } else { packetData->discardSubTree(); } - return success; + + + return appendState; } QByteArray EntityItemProperties::getPackedNormals() const { @@ -1664,7 +1665,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity); @@ -2471,3 +2472,110 @@ bool EntityItemProperties::parentRelatedPropertyChanged() const { bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const { return parentRelatedPropertyChanged() || dimensionsChanged(); } + +// Checking Certifiable Properties +#define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N() +#define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString() +#define ADD_INT_PROPERTY(n, N) if (get##N() != 0) json[#n] = (get##N() == (quint32) -1) ? -1.0 : ((double) get##N()) +QByteArray EntityItemProperties::getStaticCertificateJSON() const { + // Produce a compact json of every non-default static certificate property, with the property names in alphabetical order. + // The static certificate properties include all an only those properties that cannot be changed without altering the identity + // of the entity as reviewed during the certification submission. + + QJsonObject json; + if (!getAnimation().getURL().isEmpty()) { + json["animationURL"] = getAnimation().getURL(); + } + ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); + ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); + ADD_INT_PROPERTY(editionNumber, EditionNumber); + ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber); + ADD_STRING_PROPERTY(itemArtist, ItemArtist); + ADD_STRING_PROPERTY(itemCategories, ItemCategories); + ADD_STRING_PROPERTY(itemDescription, ItemDescription); + ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense); + ADD_STRING_PROPERTY(itemName, ItemName); + ADD_INT_PROPERTY(limitedRun, LimitedRun); + ADD_STRING_PROPERTY(marketplaceID, MarketplaceID); + ADD_STRING_PROPERTY(modelURL, ModelURL); + ADD_STRING_PROPERTY(script, Script); + ADD_ENUM_PROPERTY(shapeType, ShapeType); + json["type"] = EntityTypes::getEntityTypeName(getType()); + + return QJsonDocument(json).toJson(QJsonDocument::Compact); +} +QByteArray EntityItemProperties::getStaticCertificateHash() const { + return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256); +} + +bool EntityItemProperties::verifyStaticCertificateProperties() { + // True IIF a non-empty certificateID matches the static certificate json. + // I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash. + + if (getCertificateID().isEmpty()) { + return false; + } + + const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8(); + const unsigned char* marketplacePublicKey = reinterpret_cast(marketplacePublicKeyByteArray.constData()); + int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length(); + + BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength); + EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (evp_key) { + RSA* rsa = EVP_PKEY_get1_RSA(evp_key); + if (rsa) { + const QByteArray digestByteArray = getStaticCertificateHash(); + const unsigned char* digest = reinterpret_cast(digestByteArray.constData()); + int digestLength = digestByteArray.length(); + + const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8()); + const unsigned char* signature = reinterpret_cast(signatureByteArray.constData()); + int signatureLength = signatureByteArray.length(); + + ERR_clear_error(); + bool answer = RSA_verify(NID_sha256, + digest, + digestLength, + signature, + signatureLength, + rsa); + long error = ERR_get_error(); + if (error != 0) { + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str + << "\nStatic Cert JSON:" << getStaticCertificateJSON() + << "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength + << "\nDigest:" << digest << "\nDigest Length:" << digestLength + << "\nSignature:" << signature << "\nSignature Length:" << signatureLength; + } + RSA_free(rsa); + if (bio) { + BIO_free(bio); + } + if (evp_key) { + EVP_PKEY_free(evp_key); + } + return answer; + } else { + if (bio) { + BIO_free(bio); + } + if (evp_key) { + EVP_PKEY_free(evp_key); + } + long error = ERR_get_error(); + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; + return false; + } + } else { + if (bio) { + BIO_free(bio); + } + long error = ERR_get_error(); + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; + return false; + } +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 6547026e5c..732dbdf69f 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -262,8 +262,8 @@ public: float getLocalRenderAlpha() const { return _localRenderAlpha; } void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } - static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, - QByteArray& buffer); + static OctreeElement::AppendState encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, + QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties); static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer); @@ -336,6 +336,10 @@ public: QByteArray getPackedStrokeColors() const; QByteArray packStrokeColors(const QVector& strokeColors) const; + QByteArray getStaticCertificateJSON() const; + QByteArray getStaticCertificateHash() const; + bool verifyStaticCertificateProperties(); + protected: QString getCollisionMaskAsString() const; void setCollisionMaskFromString(const QString& maskString); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index f0f22b0091..35d40b669a 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -21,8 +21,7 @@ enum EntityPropertyList { // these properties are supported by the EntityItem base class PROP_VISIBLE, PROP_POSITION, - PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams - PROP_DIMENSIONS = PROP_RADIUS, + PROP_DIMENSIONS, PROP_ROTATION, PROP_DENSITY, PROP_VELOCITY, @@ -47,13 +46,13 @@ enum EntityPropertyList { PROP_ANGULAR_VELOCITY, PROP_ANGULAR_DAMPING, PROP_COLLISIONLESS, - PROP_DYNAMIC, + PROP_DYNAMIC, // 24 // property used by Light entity PROP_IS_SPOTLIGHT, PROP_DIFFUSE_COLOR, - PROP_AMBIENT_COLOR_UNUSED, - PROP_SPECULAR_COLOR_UNUSED, + PROP_AMBIENT_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol + PROP_SPECULAR_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION PROP_LINEAR_ATTENUATION_UNUSED, PROP_QUADRATIC_ATTENUATION_UNUSED, @@ -61,30 +60,30 @@ enum EntityPropertyList { PROP_CUTOFF, // available to all entities - PROP_LOCKED, + PROP_LOCKED, // 34 PROP_TEXTURES, // used by Model entities - PROP_ANIMATION_SETTINGS, // used by Model entities - PROP_USER_DATA, // all entities + PROP_ANIMATION_SETTINGS_UNUSED, // FIXME - No longer used, can remove and bump protocol + PROP_USER_DATA, // all entities -- 37 PROP_SHAPE_TYPE, // used by Model + zones entities // used by ParticleEffect entities - PROP_MAX_PARTICLES, - PROP_LIFESPAN, + PROP_MAX_PARTICLES, // 39 + PROP_LIFESPAN, // 40 -- used by all entities PROP_EMIT_RATE, PROP_EMIT_SPEED, PROP_EMIT_STRENGTH, - PROP_EMIT_ACCELERATION, - PROP_PARTICLE_RADIUS, + PROP_EMIT_ACCELERATION, // FIXME - doesn't seem to get set in mark all changed???? + PROP_PARTICLE_RADIUS, // 45!! PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities PROP_MARKETPLACE_ID, // all entities PROP_ACCELERATION, // all entities PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID - PROP_NAME, // all entities + PROP_NAME, // all entities -- 50 PROP_COLLISION_SOUND_URL, PROP_RESTITUTION, - PROP_FRICTION, + PROP_FRICTION, // 53 PROP_VOXEL_VOLUME_SIZE, PROP_VOXEL_DATA, @@ -96,7 +95,7 @@ enum EntityPropertyList { // used by hyperlinks PROP_HREF, - PROP_DESCRIPTION, + PROP_DESCRIPTION, // 61 PROP_FACE_CAMERA, PROP_SCRIPT_TIMESTAMP, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index c83a5f60a1..998a2d4dfe 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1827,7 +1827,7 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { - result = entity->verifyStaticCertificateProperties(); + result = entity->getProperties().verifyStaticCertificateProperties(); } }); } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 9397f38cdd..333a514377 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1195,7 +1195,6 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin QWriteLocker locker(&_certNonceMapLock); _certNonceMap.insert(certID, nonce); - qCDebug(entities) << "Challenging ownership of Cert ID" << certID << "by encrypting and sending nonce" << nonce << "to owner."; return encryptedText; } else { @@ -1206,9 +1205,7 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin } } -bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce) { - - EntityItemID id; +bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id) { { QReadLocker certIdMapLocker(&_entityCertificateIDMapLock); id = _entityCertificateIDMap.value(certID); @@ -1221,19 +1218,116 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr } bool verificationSuccess = (actualNonce == decryptedNonce); - if (!verificationSuccess) { - if (!id.isNull()) { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed; deleting entity" << id - << "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce; - deleteEntity(id, true); - } + + if (verificationSuccess) { + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded."; } else { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded; keeping entity" << id; + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed." + << "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce; } return verificationSuccess; } +void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + int certIDByteArraySize; + int encryptedTextByteArraySize; + int nodeToChallengeByteArraySize; + + message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&encryptedTextByteArraySize); + message.readPrimitive(&nodeToChallengeByteArraySize); + + QByteArray certID(message.read(certIDByteArraySize)); + QByteArray encryptedText(message.read(encryptedTextByteArraySize)); + QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize)); + + sendChallengeOwnershipRequestPacket(certID, encryptedText, nodeToChallenge, sourceNode); +} + +void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + auto nodeList = DependencyManager::get(); + + int certIDByteArraySize; + int decryptedTextByteArraySize; + int challengingNodeUUIDByteArraySize; + + message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&decryptedTextByteArraySize); + message.readPrimitive(&challengingNodeUUIDByteArraySize); + + QByteArray certID(message.read(certIDByteArraySize)); + QByteArray decryptedText(message.read(decryptedTextByteArraySize)); + QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize)); + + auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, + certIDByteArraySize + decryptedText.length() + 2 * sizeof(int), + true); + challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipReplyPacket->writePrimitive(decryptedText.length()); + challengeOwnershipReplyPacket->write(certID); + challengeOwnershipReplyPacket->write(decryptedText); + + nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode))); +} + +void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { + // 1. Encrypt a nonce with the owner's public key + auto nodeList = DependencyManager::get(); + + QByteArray encryptedText = computeEncryptedNonce(certID, ownerKey); + + if (encryptedText == "") { + qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity..."; + deleteEntity(entityItemID, true); + } else { + qCDebug(entities) << "Challenging ownership of Cert ID" << certID; + // 2. Send the encrypted text to the rezzing avatar's node + QByteArray certIDByteArray = certID.toUtf8(); + int certIDByteArraySize = certIDByteArray.size(); + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, + certIDByteArraySize + encryptedText.length() + 2 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedText.length()); + challengeOwnershipPacket->write(certIDByteArray); + challengeOwnershipPacket->write(encryptedText); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); + + // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); + return; + } else { + startChallengeOwnershipTimer(entityItemID); + } + } +} + +void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) { + auto nodeList = DependencyManager::get(); + + // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants + // to make sure belongs to Avatar B. + QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); + + int certIDByteArraySize = certID.length(); + int encryptedTextByteArraySize = encryptedText.length(); + int senderNodeUUIDSize = senderNodeUUID.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + encryptedTextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); + challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(encryptedText); + challengeOwnershipPacket->write(senderNodeUUID); + + nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(QUuid::fromRfc4122(nodeToChallenge)))); +} + void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) { // Start owner verification. auto nodeList = DependencyManager::get(); @@ -1279,33 +1373,11 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt } } else { // Second, challenge ownership of the PoP cert - // 1. Encrypt a nonce with the owner's public key - QByteArray encryptedText = computeEncryptedNonce(certID, jsonObject["transfer_recipient_key"].toString()); + sendChallengeOwnershipPacket(certID, + jsonObject["transfer_recipient_key"].toString(), + entityItemID, + senderNode); - if (encryptedText == "") { - qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity..."; - deleteEntity(entityItemID, true); - } else { - // 2. Send the encrypted text to the rezzing avatar's node - QByteArray certIDByteArray = certID.toUtf8(); - int certIDByteArraySize = certIDByteArray.size(); - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, - certIDByteArraySize + encryptedText.length() + 2 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(encryptedText.length()); - challengeOwnershipPacket->write(certIDByteArray); - challengeOwnershipPacket->write(encryptedText); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); - - // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); - return; - } else { - startChallengeOwnershipTimer(entityItemID); - } - } } } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID @@ -1329,7 +1401,12 @@ void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const emit killChallengeOwnershipTimeoutTimer(certID); - verifyDecryptedNonce(certID, decryptedText); + EntityItemID id; + if (!verifyDecryptedNonce(certID, decryptedText, id)) { + if (!id.isNull()) { + deleteEntity(id, true); + } + } } int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, @@ -1528,7 +1605,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c _totalCreates++; if (newEntity && isCertified && getIsServer()) { - if (!newEntity->verifyStaticCertificateProperties()) { + if (!properties.verifyStaticCertificateProperties()) { qCDebug(entities) << "User" << senderNode->getUUID() << "attempted to add a certified entity with ID" << entityItemID << "which failed" << "static certificate verification."; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 518cde9a59..86bfe984f6 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -93,6 +93,8 @@ public: void fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties); virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) override; + virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; + virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -273,6 +275,9 @@ public: static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; + QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey); + bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id); + signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); @@ -375,8 +380,8 @@ protected: Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); private: - QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey); - bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce); + void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); + void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); }; diff --git a/libraries/entities/src/HazePropertyGroup.cpp b/libraries/entities/src/HazePropertyGroup.cpp index 996d2e0cd3..f137fca5ce 100644 --- a/libraries/entities/src/HazePropertyGroup.cpp +++ b/libraries/entities/src/HazePropertyGroup.cpp @@ -15,19 +15,6 @@ #include "EntityItemProperties.h" #include "EntityItemPropertiesMacros.h" -const float HazePropertyGroup::DEFAULT_HAZE_RANGE{ 1000.0f }; -const xColor HazePropertyGroup::DEFAULT_HAZE_COLOR{ 128, 154, 179 }; // Bluish -const xColor HazePropertyGroup::DEFAULT_HAZE_GLARE_COLOR{ 255, 229, 179 }; // Yellowish -const float HazePropertyGroup::DEFAULT_HAZE_GLARE_ANGLE{ 20.0 }; - -const float HazePropertyGroup::DEFAULT_HAZE_CEILING{ 200.0f }; -const float HazePropertyGroup::DEFAULT_HAZE_BASE_REF{ 0.0f }; - -const float HazePropertyGroup::DEFAULT_HAZE_BACKGROUND_BLEND{ 0.0f }; - -const float HazePropertyGroup::DEFAULT_HAZE_KEYLIGHT_RANGE{ 1000.0 }; -const float HazePropertyGroup::DEFAULT_HAZE_KEYLIGHT_ALTITUDE{ 200.0f }; - void HazePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_HAZE_RANGE, Haze, haze, HazeRange, hazeRange); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_HAZE_COLOR, Haze, haze, HazeColor, hazeColor); @@ -306,6 +293,7 @@ EntityPropertyFlags HazePropertyGroup::getEntityProperties(EncodeBitstreamParams requestedProperties += PROP_HAZE_ENABLE_GLARE; requestedProperties += PROP_HAZE_GLARE_ANGLE; + requestedProperties += PROP_HAZE_ALTITUDE_EFFECT; requestedProperties += PROP_HAZE_CEILING; requestedProperties += PROP_HAZE_BASE_REF; diff --git a/libraries/entities/src/HazePropertyGroup.h b/libraries/entities/src/HazePropertyGroup.h index cdd36ff7ef..939391caf9 100644 --- a/libraries/entities/src/HazePropertyGroup.h +++ b/libraries/entities/src/HazePropertyGroup.h @@ -27,6 +27,19 @@ class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; +static const float INITIAL_HAZE_RANGE{ 1000.0f }; +static const xColor initialHazeGlareColorXcolor{ 255, 229, 179 }; +static const xColor initialHazeColorXcolor{ 128, 154, 179 }; +static const float INITIAL_HAZE_GLARE_ANGLE{ 20.0f }; + +static const float INITIAL_HAZE_BASE_REFERENCE{ 0.0f }; +static const float INITIAL_HAZE_HEIGHT{ 200.0f }; + +static const float INITIAL_HAZE_BACKGROUND_BLEND{ 0.0f }; + +static const float INITIAL_KEY_LIGHT_RANGE{ 1000.0f }; +static const float INITIAL_KEY_LIGHT_ALTITUDE{ 200.0f }; + class HazePropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers @@ -74,38 +87,25 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - static const float DEFAULT_HAZE_RANGE; - static const xColor DEFAULT_HAZE_COLOR; - static const xColor DEFAULT_HAZE_GLARE_COLOR; - static const float DEFAULT_HAZE_GLARE_ANGLE; - - static const float DEFAULT_HAZE_CEILING; - static const float DEFAULT_HAZE_BASE_REF; - - static const float DEFAULT_HAZE_BACKGROUND_BLEND; - - static const float DEFAULT_HAZE_KEYLIGHT_RANGE; - static const float DEFAULT_HAZE_KEYLIGHT_ALTITUDE; - // Range only parameters - DEFINE_PROPERTY(PROP_HAZE_RANGE, HazeRange, hazeRange, float, DEFAULT_HAZE_RANGE); - DEFINE_PROPERTY_REF(PROP_HAZE_COLOR, HazeColor, hazeColor, xColor, DEFAULT_HAZE_COLOR); - DEFINE_PROPERTY_REF(PROP_HAZE_GLARE_COLOR, HazeGlareColor, hazeGlareColor, xColor, DEFAULT_HAZE_GLARE_COLOR); + DEFINE_PROPERTY(PROP_HAZE_RANGE, HazeRange, hazeRange, float, INITIAL_HAZE_RANGE); + DEFINE_PROPERTY_REF(PROP_HAZE_COLOR, HazeColor, hazeColor, xColor, initialHazeColorXcolor); + DEFINE_PROPERTY_REF(PROP_HAZE_GLARE_COLOR, HazeGlareColor, hazeGlareColor, xColor, initialHazeGlareColorXcolor); DEFINE_PROPERTY(PROP_HAZE_ENABLE_GLARE, HazeEnableGlare, hazeEnableGlare, bool, false); - DEFINE_PROPERTY_REF(PROP_HAZE_GLARE_ANGLE, HazeGlareAngle, hazeGlareAngle, float, DEFAULT_HAZE_GLARE_ANGLE); + DEFINE_PROPERTY_REF(PROP_HAZE_GLARE_ANGLE, HazeGlareAngle, hazeGlareAngle, float, INITIAL_HAZE_GLARE_ANGLE); // Altitude parameters DEFINE_PROPERTY(PROP_HAZE_ALTITUDE_EFFECT, HazeAltitudeEffect, hazeAltitudeEffect, bool, false); - DEFINE_PROPERTY_REF(PROP_HAZE_CEILING, HazeCeiling, hazeCeiling, float, DEFAULT_HAZE_CEILING); - DEFINE_PROPERTY_REF(PROP_HAZE_BASE_REF, HazeBaseRef, hazeBaseRef, float, DEFAULT_HAZE_BASE_REF); + DEFINE_PROPERTY_REF(PROP_HAZE_CEILING, HazeCeiling, hazeCeiling, float, INITIAL_HAZE_BASE_REFERENCE + INITIAL_HAZE_HEIGHT); + DEFINE_PROPERTY_REF(PROP_HAZE_BASE_REF, HazeBaseRef, hazeBaseRef, float, INITIAL_HAZE_BASE_REFERENCE); // Background (skybox) blend value - DEFINE_PROPERTY_REF(PROP_HAZE_BACKGROUND_BLEND, HazeBackgroundBlend, hazeBackgroundBlend, float, DEFAULT_HAZE_BACKGROUND_BLEND); + DEFINE_PROPERTY_REF(PROP_HAZE_BACKGROUND_BLEND, HazeBackgroundBlend, hazeBackgroundBlend, float, INITIAL_HAZE_BACKGROUND_BLEND); // hazeDirectional light attenuation DEFINE_PROPERTY(PROP_HAZE_ATTENUATE_KEYLIGHT, HazeAttenuateKeyLight, hazeAttenuateKeyLight, bool, false); - DEFINE_PROPERTY_REF(PROP_HAZE_KEYLIGHT_RANGE, HazeKeyLightRange, hazeKeyLightRange, float, DEFAULT_HAZE_KEYLIGHT_RANGE); - DEFINE_PROPERTY_REF(PROP_HAZE_KEYLIGHT_ALTITUDE, HazeKeyLightAltitude, hazeKeyLightAltitude, float, DEFAULT_HAZE_KEYLIGHT_ALTITUDE); + DEFINE_PROPERTY_REF(PROP_HAZE_KEYLIGHT_RANGE, HazeKeyLightRange, hazeKeyLightRange, float, INITIAL_KEY_LIGHT_RANGE); + DEFINE_PROPERTY_REF(PROP_HAZE_KEYLIGHT_ALTITUDE, HazeKeyLightAltitude, hazeKeyLightAltitude, float, INITIAL_KEY_LIGHT_ALTITUDE); }; #endif // hifi_HazePropertyGroup_h diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 9a1a500a54..4115a606df 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -51,12 +51,12 @@ namespace entity { } } -// shapeCalculator is a hook for external code that knows how to configure a ShapeInfo +// hullShapeCalculator is a hook for external code that knows how to configure a ShapeInfo // for given entity::Shape and dimensions -ShapeEntityItem::ShapeInfoCalculator shapeCalculator = nullptr; +ShapeEntityItem::ShapeInfoCalculator hullShapeCalculator = nullptr; void ShapeEntityItem::setShapeInfoCalulator(ShapeEntityItem::ShapeInfoCalculator callback) { - shapeCalculator = callback; + hullShapeCalculator = callback; } ShapeEntityItem::Pointer ShapeEntityItem::baseFactory(const EntityItemID& entityID, const EntityItemProperties& properties) { @@ -104,6 +104,14 @@ void ShapeEntityItem::setShape(const entity::Shape& shape) { case entity::Shape::Sphere: _type = EntityTypes::Sphere; break; + case entity::Shape::Circle: + // Circle is implicitly flat so we enforce flat dimensions + setDimensions(getDimensions()); + break; + case entity::Shape::Quad: + // Quad is implicitly flat so we enforce flat dimensions + setDimensions(getDimensions()); + break; default: _type = EntityTypes::Shape; break; @@ -196,6 +204,18 @@ void ShapeEntityItem::setColor(const QColor& value) { setAlpha(value.alpha()); } +void ShapeEntityItem::setDimensions(const glm::vec3& value) { + const float MAX_FLAT_DIMENSION = 0.0001f; + if ((_shape == entity::Shape::Circle || _shape == entity::Shape::Quad) && value.y > MAX_FLAT_DIMENSION) { + // enforce flatness in Y + glm::vec3 newDimensions = value; + newDimensions.y = MAX_FLAT_DIMENSION; + EntityItem::setDimensions(newDimensions); + } else { + EntityItem::setDimensions(value); + } +} + bool ShapeEntityItem::supportsDetailedRayIntersection() const { return _shape == entity::Sphere; } @@ -249,19 +269,13 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { const glm::vec3 entityDimensions = getDimensions(); switch (_shape){ - case entity::Shape::Quad: { - // Not in GeometryCache::buildShapes, unsupported. - _collisionShapeType = SHAPE_TYPE_ELLIPSOID; - //TODO WL21389: Add a SHAPE_TYPE_QUAD ShapeType and treat - // as a special box (later if desired support) - } - break; + case entity::Shape::Quad: + // Quads collide like flat Cubes case entity::Shape::Cube: { _collisionShapeType = SHAPE_TYPE_BOX; } break; case entity::Shape::Sphere: { - float diameter = entityDimensions.x; const float MIN_DIAMETER = 0.001f; const float MIN_RELATIVE_SPHERICAL_ERROR = 0.001f; @@ -275,25 +289,28 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { } } break; - case entity::Shape::Circle: { - _collisionShapeType = SHAPE_TYPE_CIRCLE; - } - break; + case entity::Shape::Circle: + // Circles collide like flat Cylinders case entity::Shape::Cylinder: { - _collisionShapeType = SHAPE_TYPE_CYLINDER_Y; - // TODO WL21389: determine if rotation is axis-aligned - //const Transform::Quat & rot = _transform.getRotation(); - - // TODO WL21389: some way to tell apart SHAPE_TYPE_CYLINDER_Y, _X, _Z based on rotation and - // hull ( or dimensions, need circular cross section) - // Should allow for minor variance along axes? - + float diameter = entityDimensions.x; + const float MIN_DIAMETER = 0.001f; + const float MIN_RELATIVE_SPHERICAL_ERROR = 0.001f; + if (diameter > MIN_DIAMETER + && fabsf(diameter - entityDimensions.z) / diameter < MIN_RELATIVE_SPHERICAL_ERROR) { + _collisionShapeType = SHAPE_TYPE_SPHERE; + } else if (hullShapeCalculator) { + hullShapeCalculator(this, info); + _collisionShapeType = SHAPE_TYPE_SIMPLE_HULL; + } else { + // woops, someone forgot to hook up the hullShapeCalculator()! + // final fallback is ellipsoid + _collisionShapeType = SHAPE_TYPE_ELLIPSOID; + } } break; case entity::Shape::Cone: { - if (shapeCalculator) { - shapeCalculator(this, info); - // shapeCalculator only supports convex shapes (e.g. SHAPE_TYPE_HULL) + if (hullShapeCalculator) { + hullShapeCalculator(this, info); _collisionShapeType = SHAPE_TYPE_SIMPLE_HULL; } else { _collisionShapeType = SHAPE_TYPE_ELLIPSOID; @@ -304,9 +321,8 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { case entity::Shape::Triangle: case entity::Shape::Hexagon: case entity::Shape::Octagon: { - if (shapeCalculator) { - shapeCalculator(this, info); - // shapeCalculator only supports convex shapes (e.g. SHAPE_TYPE_HULL) + if (hullShapeCalculator) { + hullShapeCalculator(this, info); _collisionShapeType = SHAPE_TYPE_SIMPLE_HULL; } else { _collisionShapeType = SHAPE_TYPE_ELLIPSOID; @@ -318,9 +334,8 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { case entity::Shape::Octahedron: case entity::Shape::Dodecahedron: case entity::Shape::Icosahedron: { - if ( shapeCalculator ) { - shapeCalculator(this, info); - // shapeCalculator only supports convex shapes (e.g. SHAPE_TYPE_HULL) + if ( hullShapeCalculator ) { + hullShapeCalculator(this, info); _collisionShapeType = SHAPE_TYPE_SIMPLE_HULL; } else { _collisionShapeType = SHAPE_TYPE_ELLIPSOID; @@ -330,7 +345,7 @@ void ShapeEntityItem::computeShapeInfo(ShapeInfo& info) { case entity::Shape::Torus: { // Not in GeometryCache::buildShapes, unsupported. _collisionShapeType = SHAPE_TYPE_ELLIPSOID; - //TODO WL21389: SHAPE_TYPE_SIMPLE_HULL and pointCollection (later if desired support) + //TODO handle this shape more correctly } break; default: { diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index a88a2098e9..20e36c88e6 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -80,6 +80,8 @@ public: const rgbColor& getColor() const { return _color; } void setColor(const rgbColor& value); + void setDimensions(const glm::vec3& value) override; + xColor getXColor() const; void setColor(const xColor& value); diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 588c1f9386..8b3d22daaf 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -330,103 +330,3 @@ void ZoneEntityItem::setHazeMode(const uint32_t value) { uint32_t ZoneEntityItem::getHazeMode() const { return _hazeMode; } - -void ZoneEntityItem::setHazeRange(const float hazeRange) { - _hazeRange = hazeRange; - _hazePropertiesChanged = true; -} - -float ZoneEntityItem::getHazeRange() const { - return _hazeRange; -} - -void ZoneEntityItem::setHazeColor(const xColor hazeColor) { - _hazeColor = hazeColor; - _hazePropertiesChanged = true; -} - -xColor ZoneEntityItem::getHazeColor() const { - return _hazeColor; -} - -void ZoneEntityItem::setHazeGlareColor(const xColor hazeGlareColor) { - _hazeGlareColor = hazeGlareColor; - _hazePropertiesChanged = true; -} - -xColor ZoneEntityItem::getHazeGlareColor()const { - return _hazeGlareColor; -} - -void ZoneEntityItem::setHazeEnableGlare(const bool hazeEnableGlare) { - _hazeEnableGlare = hazeEnableGlare; - _hazePropertiesChanged = true; -} - -bool ZoneEntityItem::getHazeEnableGlare()const { - return _hazeEnableGlare; -} - -void ZoneEntityItem::setHazeGlareAngle(const float hazeGlareAngle) { - _hazeGlareAngle = hazeGlareAngle; - _hazePropertiesChanged = true; -} - -float ZoneEntityItem::getHazeGlareAngle() const { - return _hazeGlareAngle; -} - -void ZoneEntityItem::setHazeCeiling(const float hazeCeiling) { - _hazeCeiling = hazeCeiling; - _hazePropertiesChanged = true; -} - -float ZoneEntityItem::getHazeCeiling() const { - return _hazeCeiling; -} - -void ZoneEntityItem::setHazeBaseRef(const float hazeBaseRef) { - _hazeBaseRef = hazeBaseRef; - _hazePropertiesChanged = true; -} - -float ZoneEntityItem::getHazeBaseRef() const { - return _hazeBaseRef; -} - -void ZoneEntityItem::setHazeBackgroundBlend(const float hazeBackgroundBlend) { - _hazeBackgroundBlend = hazeBackgroundBlend; - _hazePropertiesChanged = true; -} - -float ZoneEntityItem::getHazeBackgroundBlend() const { - return _hazeBackgroundBlend; -} - -void ZoneEntityItem::setHazeAttenuateKeyLight(const bool hazeAttenuateKeyLight) { - _hazeAttenuateKeyLight = hazeAttenuateKeyLight; - _hazePropertiesChanged = true; -} - -bool ZoneEntityItem::getHazeAttenuateKeyLight() const { - return _hazeAttenuateKeyLight; -} - -void ZoneEntityItem::setHazeKeyLightRange(const float hazeKeyLightRange) { - _hazeKeyLightRange = hazeKeyLightRange; - _hazePropertiesChanged = true; -} - -float ZoneEntityItem::getHazeKeyLightRange() const { - return _hazeKeyLightRange; -} - -void ZoneEntityItem::setHazeKeyLightAltitude(const float hazeKeyLightAltitude) { - _hazeKeyLightAltitude = hazeKeyLightAltitude; - _hazePropertiesChanged = true; -} - -float ZoneEntityItem::getHazeKeyLightAltitude() const { - return _hazeKeyLightAltitude; -} - diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index ddbb2ed914..46e8a00c24 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -73,32 +73,6 @@ public: void setHazeMode(const uint32_t value); uint32_t getHazeMode() const; - void setHazeRange(const float hazeRange); - float getHazeRange() const; - void setHazeColor(const xColor hazeColor); - xColor getHazeColor() const; - void setHazeGlareColor(const xColor hazeGlareColor); - xColor getHazeGlareColor() const; - void setHazeEnableGlare(const bool hazeEnableGlare); - bool getHazeEnableGlare() const; - void setHazeGlareAngle(const float hazeGlareAngle); - float getHazeGlareAngle() const; - - void setHazeCeiling(const float hazeCeiling); - float getHazeCeiling() const; - void setHazeBaseRef(const float hazeBaseRef); - float getHazeBaseRef() const; - - void setHazeBackgroundBlend(const float hazeBackgroundBlend); - float getHazeBackgroundBlend() const; - - void setHazeAttenuateKeyLight(const bool hazeAttenuateKeyLight); - bool getHazeAttenuateKeyLight() const; - void setHazeKeyLightRange(const float hazeKeyLightRange); - float getHazeKeyLightRange() const; - void setHazeKeyLightAltitude(const float hazeKeyLightAltitude); - float getHazeKeyLightAltitude() const; - SkyboxPropertyGroup getSkyboxProperties() const { return resultWithReadLock([&] { return _skyboxProperties; }); } const HazePropertyGroup& getHazeProperties() const { return _hazeProperties; } @@ -150,21 +124,6 @@ protected: uint32_t _hazeMode{ DEFAULT_HAZE_MODE }; - float _hazeRange{ HazePropertyGroup::DEFAULT_HAZE_RANGE }; - xColor _hazeColor{ HazePropertyGroup::DEFAULT_HAZE_COLOR }; - xColor _hazeGlareColor{ HazePropertyGroup::DEFAULT_HAZE_GLARE_COLOR }; - bool _hazeEnableGlare{ false }; - float _hazeGlareAngle{ HazePropertyGroup::DEFAULT_HAZE_GLARE_ANGLE }; - - float _hazeCeiling{ HazePropertyGroup::DEFAULT_HAZE_CEILING }; - float _hazeBaseRef{ HazePropertyGroup::DEFAULT_HAZE_BASE_REF }; - - float _hazeBackgroundBlend{ HazePropertyGroup::DEFAULT_HAZE_BACKGROUND_BLEND }; - - bool _hazeAttenuateKeyLight{ false }; - float _hazeKeyLightRange{ HazePropertyGroup::DEFAULT_HAZE_KEYLIGHT_RANGE }; - float _hazeKeyLightAltitude{ HazePropertyGroup::DEFAULT_HAZE_KEYLIGHT_ALTITUDE }; - SkyboxPropertyGroup _skyboxProperties; HazePropertyGroup _hazeProperties; StagePropertyGroup _stageProperties; diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.h b/libraries/gpu-gl/src/gpu/gl/GLShared.h index 1b898e5c22..a1cf27afa6 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShared.h +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.h @@ -110,6 +110,7 @@ static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = { GL_SHORT, GL_UNSIGNED_SHORT, GL_BYTE, + GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 192a82dafc..528a2b524b 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -212,6 +212,9 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::NUINT8: result = GL_RGBA8; break; + case gpu::NUINT2: + result = GL_RGBA2; + break; case gpu::NINT8: result = GL_RGBA8_SNORM; break; @@ -498,6 +501,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; } case gpu::COMPRESSED: + case gpu::NUINT2: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -548,6 +552,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; } case gpu::COMPRESSED: + case gpu::NUINT2: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -660,6 +665,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.format = GL_RGBA; texel.internalFormat = GL_RGBA8_SNORM; break; + case gpu::NUINT2: + texel.format = GL_RGBA; + texel.internalFormat = GL_RGBA2; + break; case gpu::NUINT32: case gpu::NINT32: case gpu::COMPRESSED: diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index 7efe4d3ed6..3b153097cf 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -19,6 +19,8 @@ const Element Element::COLOR_SRGBA_32{ VEC4, NUINT8, SRGBA }; const Element Element::COLOR_BGRA_32{ VEC4, NUINT8, BGRA }; const Element Element::COLOR_SBGRA_32{ VEC4, NUINT8, SBGRA }; +const Element Element::COLOR_RGBA_2{ VEC4, NUINT2, RGBA }; + const Element Element::COLOR_COMPRESSED_RED{ TILE4x4, COMPRESSED, COMPRESSED_BC4_RED }; const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGB }; const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 0654b23581..9d5d2fc49d 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -38,6 +38,7 @@ enum Type : uint8_t { NUINT16, NINT8, NUINT8, + NUINT2, COMPRESSED, @@ -309,6 +310,7 @@ public: static const Element COLOR_SRGBA_32; static const Element COLOR_BGRA_32; static const Element COLOR_SBGRA_32; + static const Element COLOR_RGBA_2; static const Element COLOR_R11G11B10; static const Element COLOR_RGB9E5; static const Element COLOR_COMPRESSED_RED; diff --git a/libraries/gpu/src/gpu/Framebuffer.cpp b/libraries/gpu/src/gpu/Framebuffer.cpp index f1257e7c83..4fcc9ab0f9 100755 --- a/libraries/gpu/src/gpu/Framebuffer.cpp +++ b/libraries/gpu/src/gpu/Framebuffer.cpp @@ -220,7 +220,7 @@ uint32 Framebuffer::getRenderBufferSubresource(uint32 slot) const { } } -bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { +bool Framebuffer::assignDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { if (isSwapchain()) { return false; } @@ -244,20 +244,59 @@ bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const For // assign the new one _depthStencilBuffer = TextureView(texture, subresource, format); - _bufferMask = ( _bufferMask & ~BUFFER_DEPTHSTENCIL); - if (texture) { - if (format.getSemantic() == gpu::DEPTH) { - _bufferMask |= BUFFER_DEPTH; - } else if (format.getSemantic() == gpu::STENCIL) { - _bufferMask |= BUFFER_STENCIL; - } else if (format.getSemantic() == gpu::DEPTH_STENCIL) { - _bufferMask |= BUFFER_DEPTHSTENCIL; - } - } - return true; } +bool Framebuffer::setDepthBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { + if (assignDepthStencilBuffer(texture, format, subresource)) { + _bufferMask = (_bufferMask & ~BUFFER_DEPTHSTENCIL); + if (texture) { + if (format.getSemantic() == gpu::DEPTH || format.getSemantic() == gpu::DEPTH_STENCIL) { + _bufferMask |= BUFFER_DEPTH; + } else { + return false; + } + } + + return true; + } + return false; +} + +bool Framebuffer::setStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { + if (assignDepthStencilBuffer(texture, format, subresource)) { + _bufferMask = (_bufferMask & ~BUFFER_DEPTHSTENCIL); + if (texture) { + if (format.getSemantic() == gpu::STENCIL || format.getSemantic() == gpu::DEPTH_STENCIL) { + _bufferMask |= BUFFER_STENCIL; + } else { + return false; + } + } + + return true; + } + return false; +} + +bool Framebuffer::setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource) { + if (assignDepthStencilBuffer(texture, format, subresource)) { + _bufferMask = (_bufferMask & ~BUFFER_DEPTHSTENCIL); + if (texture) { + if (format.getSemantic() == gpu::DEPTH) { + _bufferMask |= BUFFER_DEPTH; + } else if (format.getSemantic() == gpu::STENCIL) { + _bufferMask |= BUFFER_STENCIL; + } else if (format.getSemantic() == gpu::DEPTH_STENCIL) { + _bufferMask |= BUFFER_DEPTHSTENCIL; + } + } + + return true; + } + return false; +} + TexturePointer Framebuffer::getDepthStencilBuffer() const { if (isSwapchain()) { return TexturePointer(); diff --git a/libraries/gpu/src/gpu/Framebuffer.h b/libraries/gpu/src/gpu/Framebuffer.h index b3a500d68f..b3cf0fbba3 100755 --- a/libraries/gpu/src/gpu/Framebuffer.h +++ b/libraries/gpu/src/gpu/Framebuffer.h @@ -107,6 +107,8 @@ public: TexturePointer getRenderBuffer(uint32 slot) const; uint32 getRenderBufferSubresource(uint32 slot) const; + bool setDepthBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); + bool setStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); bool setDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource = 0); TexturePointer getDepthStencilBuffer() const; uint32 getDepthStencilBufferSubresource() const; @@ -168,6 +170,7 @@ protected: uint16 _numSamples = 0; void updateSize(const TexturePointer& texture); + bool assignDepthStencilBuffer(const TexturePointer& texture, const Format& format, uint32 subresource); // Non exposed Framebuffer(const Framebuffer& framebuffer) = delete; diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 9feca4a3c9..b9b8544601 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -193,13 +193,17 @@ TransformObject getTransformObject() { } <@endfunc@> -<@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@> - { // transformModelToClipPos +<@func transformModelToMonoClipPos(cameraTransform, objectTransform, modelPos, clipPos)@> + { // transformModelToMonoClipPos vec4 eyeWAPos; <$transformModelToEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos)$> - <$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos; - + } +<@endfunc@> + +<@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@> + { // transformModelToClipPos + <$transformModelToMonoClipPos($cameraTransform$, $objectTransform$, $modelPos$, $clipPos$)$> <$transformStereoClipsSpace($cameraTransform$, $clipPos$)$> } <@endfunc@> diff --git a/libraries/model/src/model/Haze.cpp b/libraries/model/src/model/Haze.cpp index 679d4ad3d1..c9c73bcee9 100644 --- a/libraries/model/src/model/Haze.cpp +++ b/libraries/model/src/model/Haze.cpp @@ -9,12 +9,30 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include -#include #include "Haze.h" using namespace model; +const float Haze::INITIAL_HAZE_RANGE{ 1000.0f }; +const float Haze::INITIAL_HAZE_HEIGHT{ 200.0f }; + +const float Haze::INITIAL_KEY_LIGHT_RANGE{ 1000.0f }; +const float Haze::INITIAL_KEY_LIGHT_ALTITUDE{ 200.0f }; + +const float Haze::INITIAL_HAZE_BACKGROUND_BLEND{ 0.0f }; + +const glm::vec3 Haze::INITIAL_HAZE_COLOR{ 0.5f, 0.6f, 0.7f }; // Bluish + +const float Haze::INITIAL_HAZE_GLARE_ANGLE{ 20.0f }; + +const glm::vec3 Haze::INITIAL_HAZE_GLARE_COLOR{ 1.0f, 0.9f, 0.7f }; + +const float Haze::INITIAL_HAZE_BASE_REFERENCE{ 0.0f }; + +const float Haze::LOG_P_005{ logf(0.05f)}; +const float Haze::LOG_P_05{ logf(0.5f) }; + Haze::Haze() { Parameters parameters; _hazeParametersBuffer = gpu::BufferView(std::make_shared(sizeof(Parameters), (const gpu::Byte*) ¶meters)); @@ -23,7 +41,7 @@ Haze::Haze() { enum HazeModes { HAZE_MODE_IS_ACTIVE = 1 << 0, HAZE_MODE_IS_ALTITUDE_BASED = 1 << 1, - HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED = 1 << 2, + HAZE_MODE_IS_KEYLIGHT_ATTENUATED = 1 << 2, HAZE_MODE_IS_MODULATE_COLOR = 1 << 3, HAZE_MODE_IS_ENABLE_LIGHT_BLEND = 1 << 4 }; @@ -55,25 +73,25 @@ void Haze::setHazeEnableGlare(const bool isHazeEnableGlare) { } } -void Haze::setDirectionalLightBlend(const float hazeDirectionalLightBlend) { +void Haze::setHazeGlareBlend(const float hazeGlareBlend) { auto& params = _hazeParametersBuffer.get(); - if (params.directionalLightBlend != hazeDirectionalLightBlend) { - _hazeParametersBuffer.edit().directionalLightBlend = hazeDirectionalLightBlend; + if (params.hazeGlareBlend != hazeGlareBlend) { + _hazeParametersBuffer.edit().hazeGlareBlend = hazeGlareBlend; } } -void Haze::setDirectionalLightColor(const glm::vec3 hazeDirectionalLightColor) { +void Haze::setHazeGlareColor(const glm::vec3 hazeGlareColor) { auto& params = _hazeParametersBuffer.get(); - if (params.directionalLightColor.r != hazeDirectionalLightColor.r) { - _hazeParametersBuffer.edit().directionalLightColor.r = hazeDirectionalLightColor.r; + if (params.hazeGlareColor.r != hazeGlareColor.r) { + _hazeParametersBuffer.edit().hazeGlareColor.r = hazeGlareColor.r; } - if (params.directionalLightColor.g != hazeDirectionalLightColor.g) { - _hazeParametersBuffer.edit().directionalLightColor.g = hazeDirectionalLightColor.g; + if (params.hazeGlareColor.g != hazeGlareColor.g) { + _hazeParametersBuffer.edit().hazeGlareColor.g = hazeGlareColor.g; } - if (params.directionalLightColor.b != hazeDirectionalLightColor.b) { - _hazeParametersBuffer.edit().directionalLightColor.b = hazeDirectionalLightColor.b; + if (params.hazeGlareColor.b != hazeGlareColor.b) { + _hazeParametersBuffer.edit().hazeGlareColor.b = hazeGlareColor.b; } } void Haze::setHazeActive(const bool isHazeActive) { @@ -99,10 +117,10 @@ void Haze::setAltitudeBased(const bool isAltitudeBased) { void Haze::setHazeAttenuateKeyLight(const bool isHazeAttenuateKeyLight) { auto& params = _hazeParametersBuffer.get(); - if (((params.hazeMode & HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED) == HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED ) && !isHazeAttenuateKeyLight) { - _hazeParametersBuffer.edit().hazeMode &= ~HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED; - } else if (((params.hazeMode & HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED) != HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED) && isHazeAttenuateKeyLight) { - _hazeParametersBuffer.edit().hazeMode |= HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED; + if (((params.hazeMode & HAZE_MODE_IS_KEYLIGHT_ATTENUATED) == HAZE_MODE_IS_KEYLIGHT_ATTENUATED) && !isHazeAttenuateKeyLight) { + _hazeParametersBuffer.edit().hazeMode &= ~HAZE_MODE_IS_KEYLIGHT_ATTENUATED; + } else if (((params.hazeMode & HAZE_MODE_IS_KEYLIGHT_ATTENUATED) != HAZE_MODE_IS_KEYLIGHT_ATTENUATED) && isHazeAttenuateKeyLight) { + _hazeParametersBuffer.edit().hazeMode |= HAZE_MODE_IS_KEYLIGHT_ATTENUATED; } } @@ -124,11 +142,11 @@ void Haze::setHazeRangeFactor(const float hazeRangeFactor) { } } -void Haze::setHazeAltitudeFactor(const float hazeAltitudeFactor) { +void Haze::setHazeAltitudeFactor(const float hazeHeightFactor) { auto& params = _hazeParametersBuffer.get(); - if (params.hazeAltitudeFactor != hazeAltitudeFactor) { - _hazeParametersBuffer.edit().hazeAltitudeFactor = hazeAltitudeFactor; + if (params.hazeHeightFactor != hazeHeightFactor) { + _hazeParametersBuffer.edit().hazeHeightFactor = hazeHeightFactor; } } @@ -156,11 +174,11 @@ void Haze::setHazeBaseReference(const float hazeBaseReference) { } } -void Haze::setHazeBackgroundBlendValue(const float hazeBackgroundBlendValue) { +void Haze::setHazeBackgroundBlend(const float hazeBackgroundBlend) { auto& params = _hazeParametersBuffer.get(); - if (params.hazeBackgroundBlendValue != hazeBackgroundBlendValue) { - _hazeParametersBuffer.edit().hazeBackgroundBlendValue = hazeBackgroundBlendValue; + if (params.hazeBackgroundBlend != hazeBackgroundBlend) { + _hazeParametersBuffer.edit().hazeBackgroundBlend = hazeBackgroundBlend; } } diff --git a/libraries/model/src/model/Haze.h b/libraries/model/src/model/Haze.h index fe606bf083..a0cc7c3bc7 100644 --- a/libraries/model/src/model/Haze.h +++ b/libraries/model/src/model/Haze.h @@ -12,71 +12,70 @@ #define hifi_model_Haze_h #include +#include + #include "Transform.h" #include "NumericalConstants.h" namespace model { - const float LOG_P_005 = (float)log(0.05); - const float LOG_P_05 = (float)log(0.5); - - // Derivation (d is distance, b is haze coefficient, f is attenuation, solve for f = 0.05 - // f = exp(-d * b) - // ln(f) = -d * b - // b = -ln(f)/d - inline glm::vec3 convertHazeRangeToHazeRangeFactor(const glm::vec3 hazeRange_m) { - return glm::vec3( - -LOG_P_005 / hazeRange_m.x, - -LOG_P_005 / hazeRange_m.y, - -LOG_P_005 / hazeRange_m.z); - } - - inline float convertHazeRangeToHazeRangeFactor(const float hazeRange_m) { return (-LOG_P_005 / hazeRange_m); } - - inline float convertHazeAltitudeToHazeAltitudeFactor(const float hazeAltitude_m) { - return -LOG_P_005 / hazeAltitude_m; - } - - // Derivation (s is the proportion of sun blend, a is the angle at which the blend is 50%, solve for m = 0.5 - // s = dot(lookAngle, sunAngle) = cos(a) - // m = pow(s, p) - // log(m) = p * log(s) - // p = log(m) / log(s) - inline float convertDirectionalLightAngleToPower(const float directionalLightAngle) { - return LOG_P_05 / (float)log(cos(RADIANS_PER_DEGREE * directionalLightAngle)); - } - - const glm::vec3 initialHazeColor{ 0.5f, 0.6f, 0.7f }; - const float initialDirectionalLightAngle_degs{ 30.0f }; - - const glm::vec3 initialDirectionalLightColor{ 1.0f, 0.9f, 0.7f }; - const float initialHazeBaseReference{ 0.0f }; - // Haze range is defined here as the range the visibility is reduced by 95% // Haze altitude is defined here as the altitude (above 0) that the haze is reduced by 95% - const float initialHazeRange_m{ 150.0f }; - const float initialHazeAltitude_m{ 150.0f }; - - const float initialHazeKeyLightRange_m{ 150.0f }; - const float initialHazeKeyLightAltitude_m{ 150.0f }; - - const float initialHazeBackgroundBlendValue{ 0.0f }; - - const glm::vec3 initialColorModulationFactor{ - convertHazeRangeToHazeRangeFactor(initialHazeRange_m), - convertHazeRangeToHazeRangeFactor(initialHazeRange_m), - convertHazeRangeToHazeRangeFactor(initialHazeRange_m) - }; class Haze { public: - using UniformBufferView = gpu::BufferView; + // Initial values + static const float INITIAL_HAZE_RANGE; + static const float INITIAL_HAZE_HEIGHT; + + static const float INITIAL_KEY_LIGHT_RANGE; + static const float INITIAL_KEY_LIGHT_ALTITUDE; + + static const float INITIAL_HAZE_BACKGROUND_BLEND; + + static const glm::vec3 INITIAL_HAZE_COLOR; + + static const float INITIAL_HAZE_GLARE_ANGLE; + + static const glm::vec3 INITIAL_HAZE_GLARE_COLOR; + + static const float INITIAL_HAZE_BASE_REFERENCE; + + static const float LOG_P_005; + static const float LOG_P_05; + + // Derivation (d is distance, b is haze coefficient, f is attenuation, solve for f = 0.05 + // f = exp(-d * b) + // ln(f) = -d * b + // b = -ln(f)/d + static inline glm::vec3 convertHazeRangeToHazeRangeFactor(const glm::vec3 hazeRange) { + return glm::vec3( + -LOG_P_005 / hazeRange.x, + -LOG_P_005 / hazeRange.y, + -LOG_P_005 / hazeRange.z); + } + + // limit range and altitude to no less than 1.0 metres + static inline float convertHazeRangeToHazeRangeFactor(const float hazeRange) { return -LOG_P_005 / glm::max(hazeRange, 1.0f); } + + static inline float convertHazeAltitudeToHazeAltitudeFactor(const float hazeHeight) { return -LOG_P_005 / glm::max(hazeHeight, 1.0f); } + + // Derivation (s is the proportion of sun blend, a is the angle at which the blend is 50%, solve for m = 0.5 + // s = dot(lookAngle, sunAngle) = cos(a) + // m = pow(s, p) + // log(m) = p * log(s) + // p = log(m) / log(s) + // limit to 0.1 degrees + static inline float convertGlareAngleToPower(const float hazeGlareAngle) { + const float GLARE_ANGLE_LIMIT = 0.1f; + return LOG_P_05 / logf(cosf(RADIANS_PER_DEGREE * glm::max(GLARE_ANGLE_LIMIT, hazeGlareAngle))); + } Haze(); void setHazeColor(const glm::vec3 hazeColor); - void setDirectionalLightBlend(const float directionalLightBlend); + void setHazeGlareBlend(const float hazeGlareBlend); - void setDirectionalLightColor(const glm::vec3 directionalLightColor); + void setHazeGlareColor(const glm::vec3 hazeGlareColor); void setHazeBaseReference(const float hazeBaseReference); void setHazeActive(const bool isHazeActive); @@ -91,23 +90,24 @@ namespace model { void setHazeKeyLightRangeFactor(const float hazeKeyLightRange); void setHazeKeyLightAltitudeFactor(const float hazeKeyLightAltitude); - void setHazeBackgroundBlendValue(const float hazeBackgroundBlendValue); + void setHazeBackgroundBlend(const float hazeBackgroundBlend); void setZoneTransform(const glm::mat4& zoneTransform); + using UniformBufferView = gpu::BufferView; UniformBufferView getHazeParametersBuffer() const { return _hazeParametersBuffer; } protected: class Parameters { public: // DO NOT CHANGE ORDER HERE WITHOUT UNDERSTANDING THE std140 LAYOUT - glm::vec3 hazeColor{ initialHazeColor }; - float directionalLightBlend{ convertDirectionalLightAngleToPower(initialDirectionalLightAngle_degs) }; + glm::vec3 hazeColor{ INITIAL_HAZE_COLOR }; + float hazeGlareBlend{ convertGlareAngleToPower(INITIAL_HAZE_GLARE_ANGLE) }; - glm::vec3 directionalLightColor{ initialDirectionalLightColor }; - float hazeBaseReference{ initialHazeBaseReference }; + glm::vec3 hazeGlareColor{ INITIAL_HAZE_GLARE_COLOR }; + float hazeBaseReference{ INITIAL_HAZE_BASE_REFERENCE }; - glm::vec3 colorModulationFactor{ initialColorModulationFactor }; + glm::vec3 colorModulationFactor; int hazeMode{ 0 }; // bit 0 - set to activate haze attenuation of fragment color // bit 1 - set to add the effect of altitude to the haze attenuation // bit 2 - set to activate directional light attenuation mode @@ -116,14 +116,14 @@ namespace model { glm::mat4 zoneTransform; // Amount of background (skybox) to display, overriding the haze effect for the background - float hazeBackgroundBlendValue{ initialHazeBackgroundBlendValue }; + float hazeBackgroundBlend{ INITIAL_HAZE_BACKGROUND_BLEND }; // The haze attenuation exponents used by both fragment and directional light attenuation - float hazeRangeFactor{ convertHazeRangeToHazeRangeFactor(initialHazeRange_m) }; - float hazeAltitudeFactor{ convertHazeAltitudeToHazeAltitudeFactor(initialHazeAltitude_m) }; + float hazeRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_HAZE_RANGE) }; + float hazeHeightFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_HAZE_HEIGHT) }; - float hazeKeyLightRangeFactor{ convertHazeRangeToHazeRangeFactor(initialHazeKeyLightRange_m) }; - float hazeKeyLightAltitudeFactor{ convertHazeAltitudeToHazeAltitudeFactor(initialHazeKeyLightAltitude_m) }; + float hazeKeyLightRangeFactor{ convertHazeRangeToHazeRangeFactor(INITIAL_KEY_LIGHT_RANGE) }; + float hazeKeyLightAltitudeFactor{ convertHazeAltitudeToHazeAltitudeFactor(INITIAL_KEY_LIGHT_ALTITUDE) }; Parameters() {} }; diff --git a/libraries/model/src/model/Stage.cpp b/libraries/model/src/model/Stage.cpp index cd2312122c..5854162cfa 100644 --- a/libraries/model/src/model/Stage.cpp +++ b/libraries/model/src/model/Stage.cpp @@ -256,10 +256,3 @@ void SunSkyStage::setSkybox(const SkyboxPointer& skybox) { _skybox = skybox; invalidate(); } - -void SunSkyStage::setHazeMode(uint32_t hazeMode) { - if (hazeMode < COMPONENT_MODE_ITEM_COUNT) { - _hazeMode = hazeMode; - invalidate(); - } -} diff --git a/libraries/model/src/model/Stage.h b/libraries/model/src/model/Stage.h index e009684c62..5f48824568 100644 --- a/libraries/model/src/model/Stage.h +++ b/libraries/model/src/model/Stage.h @@ -15,7 +15,6 @@ #include "Light.h" #include "Skybox.h" -#include "Haze.h" namespace model { @@ -175,65 +174,8 @@ public: void setSkybox(const SkyboxPointer& skybox); const SkyboxPointer& getSkybox() const { valid(); return _skybox; } - // Haze - enum HazeMode { - HAZE_OFF, - HAZE_ON, - - NUM_HAZE_MODES - }; - - void setHazeMode(uint32_t mode); - uint32_t getHazeMode() const { return _hazeMode; } - - void setHazeRange(float hazeRange) { _hazeRange = hazeRange; } - float getHazeRange() const { return _hazeRange; } - void setHazeColor(const xColor hazeColor) { _hazeColor = hazeColor; } - xColor getHazeColor() { return _hazeColor; } - void setHazeGlareColor(const xColor hazeGlareColor) { _hazeGlareColor = hazeGlareColor; } - xColor getHazeGlareColor() const { return _hazeGlareColor; } - void setHazeEnableGlare(bool hazeEnableGlare) { _hazeEnableGlare = hazeEnableGlare; } - bool getHazeEnableGlare() const { return _hazeEnableGlare; } - void setHazeGlareAngle(float hazeGlareAngle) { _hazeGlareAngle = hazeGlareAngle; } - float getHazeGlareAngle() const { return _hazeGlareAngle; } - - void setHazeAltitudeEffect(bool hazeAltitudeEffect) { _hazeAltitudeEffect = hazeAltitudeEffect; } - bool getHazeAltitudeEffect() const { return _hazeAltitudeEffect; } - void setHazeCeiling(float hazeCeiling) { _hazeCeiling = hazeCeiling; } - float getHazeCeiling() const { return _hazeCeiling; } - void setHazeBaseRef(float hazeBaseRef) { _hazeBaseRef = hazeBaseRef; } - float getHazeBaseRef() const { return _hazeBaseRef; } - - void setHazeBackgroundBlend(float hazeBackgroundBlend) { _hazeBackgroundBlend = hazeBackgroundBlend; } - float getHazeBackgroundBlend() const { return _hazeBackgroundBlend; } - - void setHazeAttenuateKeyLight(bool hazeAttenuateKeyLight) { _hazeAttenuateKeyLight = hazeAttenuateKeyLight; } - bool getHazeAttenuateKeyLight() const { return _hazeAttenuateKeyLight; } - void setHazeKeyLightRange(float hazeKeyLightRange) { _hazeKeyLightRange = hazeKeyLightRange; } - float getHazeKeyLightRange() const { return _hazeKeyLightRange; } - void setHazeKeyLightAltitude(float hazeKeyLightAltitude) { _hazeKeyLightAltitude = hazeKeyLightAltitude; } - float getHazeKeyLightAltitude() const { return _hazeKeyLightAltitude; } - protected: BackgroundMode _backgroundMode = SKY_DEFAULT; - - uint8_t _hazeMode = (uint8_t)HAZE_OFF; - - float _hazeRange; - xColor _hazeColor; - xColor _hazeGlareColor; - bool _hazeEnableGlare; - float _hazeGlareAngle; - - bool _hazeAltitudeEffect; - float _hazeCeiling; - float _hazeBaseRef; - - float _hazeBackgroundBlend; - - bool _hazeAttenuateKeyLight; - float _hazeKeyLightRange; - float _hazeKeyLightAltitude; LightPointer _sunLight; mutable SkyboxPointer _skybox; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 1a51440e91..b884dcba17 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -667,8 +667,11 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change."; } } - - emit locationChangeRequired(newPosition, orientationChanged, newOrientation, shouldFace); + + emit locationChangeRequired(newPosition, orientationChanged, + LookupTrigger::VisitUserFromPAL ? cancelOutRollAndPitch(newOrientation): newOrientation, + shouldFace + ); } else { qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value."; @@ -732,13 +735,14 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup return hostChanged; } -void AddressManager::goToUser(const QString& username) { +void AddressManager::goToUser(const QString& username, bool shouldMatchOrientation) { QString formattedUsername = QUrl::toPercentEncoding(username); - // for history storage handling we remember how this lookup was trigged - for a username it's always user input + // for history storage handling we remember how this lookup was triggered - for a username it's always user input QVariantMap requestParams; - requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(LookupTrigger::UserInput)); - + requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast( + shouldMatchOrientation ? LookupTrigger::UserInput : LookupTrigger::VisitUserFromPAL + )); // this is a username - pull the captured name and lookup that user's location DependencyManager::get()->sendRequest(GET_USER_LOCATION.arg(formattedUsername), AccountManagerAuth::Optional, @@ -840,8 +844,8 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // and do not but it into the back stack _forwardStack.push(currentAddress()); } else { - if (trigger == LookupTrigger::UserInput) { - // anyime the user has manually looked up an address we know we should clear the forward stack + if (trigger == LookupTrigger::UserInput || trigger == LookupTrigger::VisitUserFromPAL) { + // anyime the user has actively triggered an address we know we should clear the forward stack _forwardStack.clear(); emit goForwardPossible(false); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 366fc5dfab..2f3d896509 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -54,7 +54,8 @@ public: DomainPathResponse, Internal, AttemptedRefresh, - Suggestions + Suggestions, + VisitUserFromPAL }; bool isConnected(); @@ -93,7 +94,7 @@ public slots: void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); } - void goToUser(const QString& username); + void goToUser(const QString& username, bool shouldMatchOrientation = true); void refreshPreviousLookup(); diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 0bb0cee5d2..0c210e4360 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -16,8 +16,8 @@ namespace NetworkingConstants { // If you want to use STAGING instead of STABLE, - // don't forget to ALSO change the Domain Server Metaverse Server URL, which is at the top of: - // \domain-server\resources\web\settings\js\settings.js + // don't forget to ALSO change the Domain Server Metaverse Server URL inside of: + // \domain-server\resources\web\js\shared.js const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com"); const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com"); const QUrl METAVERSE_SERVER_URL = METAVERSE_SERVER_URL_STABLE; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e00b4139c3..21b4ae8878 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -124,6 +124,8 @@ public: OctreeFileReplacementFromUrl, ChallengeOwnership, EntityScriptCallMethod, + ChallengeOwnershipRequest, + ChallengeOwnershipReply, NUM_PACKET_TYPE }; diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 2761dffb1b..ec6a0e810d 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -212,6 +212,8 @@ public: virtual bool handlesEditPacketType(PacketType packetType) const { return false; } virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } + virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } + virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual bool recurseChildrenWithData() const { return true; } diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 493dfdcff5..b5b4a161ef 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -68,8 +68,8 @@ bool OctreePacketData::append(const unsigned char* data, int length) { _dirty = true; } - const bool wantDebug = false; - if (wantDebug && !success) { + #ifdef WANT_DEBUG + if (!success) { qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING...."; qCDebug(octree) << " length=" << length; qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable; @@ -77,6 +77,7 @@ bool OctreePacketData::append(const unsigned char* data, int length) { qCDebug(octree) << " _targetSize=" << _targetSize; qCDebug(octree) << " _bytesReserved=" << _bytesReserved; } + #endif return success; } @@ -647,6 +648,13 @@ void OctreePacketData::debugContent() { printf("\n"); } +void OctreePacketData::debugBytes() { + qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable; + qCDebug(octree) << " _bytesInUse=" << _bytesInUse; + qCDebug(octree) << " _targetSize=" << _targetSize; + qCDebug(octree) << " _bytesReserved=" << _bytesReserved; +} + int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) { uint16_t length; memcpy(&length, dataBytes, sizeof(length)); diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index ed6a49941b..37c171504b 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -240,6 +240,7 @@ public: /// displays contents for debugging void debugContent(); + void debugBytes(); static quint64 getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content static quint64 getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 81a63a696c..bbd0350baf 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -12,21 +12,7 @@ #ifndef hifi_OctreeQuery_h #define hifi_OctreeQuery_h -/* VS2010 defines stdint.h, but not inttypes.h */ -#if defined(_MSC_VER) -typedef signed char int8_t; -typedef signed short int16_t; -typedef signed int int32_t; -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned int uint32_t; -typedef signed long long int64_t; -typedef unsigned long long quint64; -#define PRId64 "I64d" -#else #include -#endif - #include #include @@ -45,7 +31,7 @@ public: virtual ~OctreeQuery() {} int getBroadcastData(unsigned char* destinationBuffer); - int parseData(ReceivedMessage& message) override; + virtual int parseData(ReceivedMessage& message) override; // getters for camera details const glm::vec3& getCameraPosition() const { return _cameraPosition; } diff --git a/libraries/octree/src/OctreeQueryNode.cpp b/libraries/octree/src/OctreeQueryNode.cpp index c26b4ce77b..941bb6b536 100644 --- a/libraries/octree/src/OctreeQueryNode.cpp +++ b/libraries/octree/src/OctreeQueryNode.cpp @@ -18,6 +18,12 @@ #include #include +int OctreeQueryNode::parseData(ReceivedMessage& message) { + // set our flag to indicate that we've parsed for this query at least once + _hasReceivedFirstQuery = true; + + return OctreeQuery::parseData(message); +} void OctreeQueryNode::nodeKilled() { _isShuttingDown = true; diff --git a/libraries/octree/src/OctreeQueryNode.h b/libraries/octree/src/OctreeQueryNode.h index fd89a89949..fac118c628 100644 --- a/libraries/octree/src/OctreeQueryNode.h +++ b/libraries/octree/src/OctreeQueryNode.h @@ -35,6 +35,8 @@ public: void init(); // called after creation to set up some virtual items virtual PacketType getMyPacketType() const = 0; + virtual int parseData(ReceivedMessage& message) override; + void resetOctreePacket(); // resets octree packet to after "V" header void writeToPacket(const unsigned char* buffer, unsigned int bytes); // writes to end of packet @@ -106,6 +108,8 @@ public: bool shouldForceFullScene() const { return _shouldForceFullScene; } void setShouldForceFullScene(bool shouldForceFullScene) { _shouldForceFullScene = shouldForceFullScene; } + bool hasReceivedFirstQuery() const { return _hasReceivedFirstQuery; } + private: OctreeQueryNode(const OctreeQueryNode &); OctreeQueryNode& operator= (const OctreeQueryNode&); @@ -153,6 +157,8 @@ private: QJsonObject _lastCheckJSONParameters; bool _shouldForceFullScene { false }; + + bool _hasReceivedFirstQuery { false }; }; #endif // hifi_OctreeQueryNode_h diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index cd0fba848a..f484f32fdf 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -16,6 +16,40 @@ #include "ShapeFactory.h" #include "BulletUtil.h" + +class StaticMeshShape : public btBvhTriangleMeshShape { +public: + StaticMeshShape() = delete; + + StaticMeshShape(btTriangleIndexVertexArray* dataArray) + : btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) { + assert(_dataArray); + } + + ~StaticMeshShape() { + assert(_dataArray); + IndexedMeshArray& meshes = _dataArray->getIndexedMeshArray(); + for (int32_t i = 0; i < meshes.size(); ++i) { + btIndexedMesh mesh = meshes[i]; + mesh.m_numTriangles = 0; + delete [] mesh.m_triangleIndexBase; + mesh.m_triangleIndexBase = nullptr; + mesh.m_numVertices = 0; + delete [] mesh.m_vertexBase; + mesh.m_vertexBase = nullptr; + } + meshes.clear(); + delete _dataArray; + _dataArray = nullptr; + } + +private: + // the StaticMeshShape owns its vertex/index data + btTriangleIndexVertexArray* _dataArray; +}; + +// the dataArray must be created before we create the StaticMeshShape + // These are the same normalized directions used by the btShapeHull class. // 12 points for the face centers of a dodecahedron plus another 30 points // for the midpoints the edges, for a total of 42. @@ -230,23 +264,6 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { return dataArray; } -// util method -void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) { - assert(dataArray); - IndexedMeshArray& meshes = dataArray->getIndexedMeshArray(); - for (int32_t i = 0; i < meshes.size(); ++i) { - btIndexedMesh mesh = meshes[i]; - mesh.m_numTriangles = 0; - delete [] mesh.m_triangleIndexBase; - mesh.m_triangleIndexBase = nullptr; - mesh.m_numVertices = 0; - delete [] mesh.m_vertexBase; - mesh.m_vertexBase = nullptr; - } - meshes.clear(); - delete dataArray; -} - const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { btCollisionShape* shape = NULL; int type = info.getType(); @@ -431,7 +448,6 @@ void ShapeFactory::deleteShape(const btCollisionShape* shape) { assert(shape); // ShapeFactory is responsible for deleting all shapes, even the const ones that are stored // in the ShapeManager, so we must cast to non-const here when deleting. - // so we cast to non-const here when deleting memory. btCollisionShape* nonConstShape = const_cast(shape); if (nonConstShape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) { btCompoundShape* compoundShape = static_cast(nonConstShape); @@ -448,14 +464,3 @@ void ShapeFactory::deleteShape(const btCollisionShape* shape) { } delete nonConstShape; } - -// the dataArray must be created before we create the StaticMeshShape -ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray) -: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) { - assert(dataArray); -} - -ShapeFactory::StaticMeshShape::~StaticMeshShape() { - deleteStaticMeshArray(_dataArray); - _dataArray = nullptr; -} diff --git a/libraries/physics/src/ShapeFactory.h b/libraries/physics/src/ShapeFactory.h index 2bf79f390c..704a7804b3 100644 --- a/libraries/physics/src/ShapeFactory.h +++ b/libraries/physics/src/ShapeFactory.h @@ -17,25 +17,11 @@ #include -// translates between ShapeInfo and btShape +// The ShapeFactory assembles and correctly disassembles btCollisionShapes. namespace ShapeFactory { const btCollisionShape* createShapeFromInfo(const ShapeInfo& info); void deleteShape(const btCollisionShape* shape); - - //btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info); - //void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray); - - class StaticMeshShape : public btBvhTriangleMeshShape { - public: - StaticMeshShape() = delete; - StaticMeshShape(btTriangleIndexVertexArray* dataArray); - ~StaticMeshShape(); - - private: - // the StaticMeshShape owns its vertex/index data - btTriangleIndexVertexArray* _dataArray; - }; }; #endif // hifi_ShapeFactory_h diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index 77716f671b..97b9e5dab1 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -32,7 +32,7 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { if (info.getType() == SHAPE_TYPE_NONE) { return nullptr; } - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { shapeRef->refCount++; @@ -50,7 +50,7 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { } // private helper method -bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) { +bool ShapeManager::releaseShapeByKey(const HashKey& key) { ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { if (shapeRef->refCount > 0) { @@ -88,7 +88,7 @@ bool ShapeManager::releaseShape(const btCollisionShape* shape) { void ShapeManager::collectGarbage() { int numShapes = _pendingGarbage.size(); for (int i = 0; i < numShapes; ++i) { - DoubleHashKey& key = _pendingGarbage[i]; + HashKey& key = _pendingGarbage[i]; ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef && shapeRef->refCount == 0) { ShapeFactory::deleteShape(shapeRef->shape); @@ -99,7 +99,7 @@ void ShapeManager::collectGarbage() { } int ShapeManager::getNumReferences(const ShapeInfo& info) const { - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); const ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { return shapeRef->refCount; diff --git a/libraries/physics/src/ShapeManager.h b/libraries/physics/src/ShapeManager.h index ed81b5e8f8..d75bb1dc4a 100644 --- a/libraries/physics/src/ShapeManager.h +++ b/libraries/physics/src/ShapeManager.h @@ -17,7 +17,29 @@ #include -#include "DoubleHashKey.h" +#include "HashKey.h" + +// The ShapeManager handles the ref-counting on shared shapes: +// +// Each object added to the physics simulation gets a corresponding btRigidBody. +// The body has a btCollisionShape that represents the contours of its collision +// surface. Multiple bodies may have the same shape. Rather than create a unique +// btCollisionShape instance for every body with a particular shape we can instead +// use a single shape instance for all of the bodies. This is called "shape +// sharing". +// +// When body needs a new shape a description of ths shape (ShapeInfo) is assembled +// and a request is sent to the ShapeManager for a corresponding btCollisionShape +// pointer. The ShapeManager will compute a hash of the ShapeInfo's data and use +// that to find the shape in its map. If it finds one it increments the ref-count +// and returns the pointer. If not it asks the ShapeFactory to create it, adds an +// entry in the map with a ref-count of 1, and returns the pointer. +// +// When a body stops using a shape the ShapeManager must be informed so it can +// decrement its ref-count. When a ref-count drops to zero the ShapeManager +// doesn't delete it right away. Instead it puts the shape's key on a list delete +// later. When that list grows big enough the ShapeManager will remove any matching +// entries that still have zero ref-count. class ShapeManager { public: @@ -41,18 +63,19 @@ public: bool hasShape(const btCollisionShape* shape) const; private: - bool releaseShapeByKey(const DoubleHashKey& key); + bool releaseShapeByKey(const HashKey& key); class ShapeReference { public: int refCount; const btCollisionShape* shape; - DoubleHashKey key; + HashKey key; ShapeReference() : refCount(0), shape(nullptr) {} }; - btHashMap _shapeMap; - btAlignedObjectArray _pendingGarbage; + // btHashMap is required because it supports memory alignment of the btCollisionShapes + btHashMap _shapeMap; + btAlignedObjectArray _pendingGarbage; }; #endif // hifi_ShapeManager_h diff --git a/libraries/render-utils/src/BackgroundStage.cpp b/libraries/render-utils/src/BackgroundStage.cpp index 2ea3683c4a..2d2c0ed150 100644 --- a/libraries/render-utils/src/BackgroundStage.cpp +++ b/libraries/render-utils/src/BackgroundStage.cpp @@ -14,6 +14,7 @@ #include std::string BackgroundStage::_stageName { "BACKGROUND_STAGE"}; +const BackgroundStage::Index BackgroundStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; BackgroundStage::Index BackgroundStage::findBackground(const BackgroundPointer& background) const { auto found = _backgroundMap.find(background); diff --git a/libraries/render-utils/src/BackgroundStage.h b/libraries/render-utils/src/BackgroundStage.h index eab7c94f0d..4e0e09db5b 100644 --- a/libraries/render-utils/src/BackgroundStage.h +++ b/libraries/render-utils/src/BackgroundStage.h @@ -27,7 +27,7 @@ public: static const std::string& getName() { return _stageName; } using Index = render::indexed_container::Index; - static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX }; + static const Index INVALID_INDEX; static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } using BackgroundPointer = model::SunSkyStagePointer; diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 07628904f1..3bb2aa2ef9 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -20,11 +20,22 @@ 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::updateTransformForCauterizedMesh( - const Transform& renderTransform, - const gpu::BufferPointer& buffer) { +void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices, const std::vector& cauterizedClusterMatrices) { + ModelMeshPartPayload::updateClusterBuffer(clusterMatrices); + + if (cauterizedClusterMatrices.size() > 1) { + if (!_cauterizedClusterBuffer) { + _cauterizedClusterBuffer = std::make_shared(cauterizedClusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) cauterizedClusterMatrices.data()); + } else { + _cauterizedClusterBuffer->setSubData(0, cauterizedClusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) cauterizedClusterMatrices.data()); + } + } +} + +void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(const Transform& renderTransform) { _cauterizedTransform = renderTransform; - _cauterizedClusterBuffer = buffer; } void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const { diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.h b/libraries/render-utils/src/CauterizedMeshPartPayload.h index 5e3135ea84..1c98f5abf3 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.h +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.h @@ -15,7 +15,9 @@ class CauterizedMeshPartPayload : public ModelMeshPartPayload { public: CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform); - void updateTransformForCauterizedMesh(const Transform& renderTransform, const gpu::BufferPointer& buffer); + void updateClusterBuffer(const std::vector& clusterMatrices, const std::vector& cauterizedClusterMatrices); + + void updateTransformForCauterizedMesh(const Transform& renderTransform); void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override; diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 47ada457a0..30121a232d 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -48,7 +48,7 @@ void CauterizedModel::createVisibleRenderItemSet() { const auto& meshes = _renderGeometry->getMeshes(); // all of our mesh vectors must match in size - if ((int)meshes.size() != _meshStates.size()) { + if (meshes.size() != _meshStates.size()) { qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; return; } @@ -57,6 +57,7 @@ void CauterizedModel::createVisibleRenderItemSet() { Q_ASSERT(_modelMeshRenderItems.isEmpty()); _modelMeshRenderItems.clear(); + _modelMeshRenderItemShapes.clear(); Transform transform; transform.setTranslation(_translation); @@ -80,6 +81,7 @@ void CauterizedModel::createVisibleRenderItemSet() { for (int partIndex = 0; partIndex < numParts; partIndex++) { auto ptr = std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); _modelMeshRenderItems << std::static_pointer_cast(ptr); + _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); shapeID++; } } @@ -102,7 +104,7 @@ void CauterizedModel::updateClusterMatrices() { _needsUpdateClusterMatrices = false; const FBXGeometry& geometry = getFBXGeometry(); - for (int i = 0; i < _meshStates.size(); i++) { + for (int i = 0; i < (int)_meshStates.size(); i++) { Model::MeshState& state = _meshStates[i]; const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { @@ -110,17 +112,6 @@ void CauterizedModel::updateClusterMatrices() { auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } - - // Once computed the cluster matrices, update the buffer(s) - if (mesh.clusters.size() > 1) { - if (!state.clusterBuffer) { - state.clusterBuffer = std::make_shared(state.clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) state.clusterMatrices.constData()); - } else { - state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) state.clusterMatrices.constData()); - } - } } // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty. @@ -143,17 +134,6 @@ void CauterizedModel::updateClusterMatrices() { } glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } - - if (!_cauterizeBoneSet.empty() && (state.clusterMatrices.size() > 1)) { - if (!state.clusterBuffer) { - state.clusterBuffer = - std::make_shared(state.clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) state.clusterMatrices.constData()); - } else { - state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) state.clusterMatrices.constData()); - } - } } } @@ -181,11 +161,11 @@ void CauterizedModel::updateRenderItems() { // queue up this work for later processing, at the end of update and just before rendering. // the application will ensure only the last lambda is actually invoked. void* key = (void*)this; - std::weak_ptr weakSelf = shared_from_this(); - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf, scale]() { + std::weak_ptr weakSelf = std::dynamic_pointer_cast(shared_from_this()); + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf]() { // do nothing, if the model has already been destroyed. auto self = weakSelf.lock(); - if (!self) { + if (!self || !self->isLoaded()) { return; } @@ -198,37 +178,28 @@ void CauterizedModel::updateRenderItems() { modelTransform.setTranslation(self->getTranslation()); modelTransform.setRotation(self->getRotation()); - Transform scaledModelTransform(modelTransform); - scaledModelTransform.setScale(scale); - - uint32_t deleteGeometryCounter = self->getGeometryCounter(); - render::Transaction transaction; - QList keys = self->getRenderItems().keys(); - foreach (auto itemID, keys) { - transaction.updateItem(itemID, [modelTransform, deleteGeometryCounter](CauterizedMeshPartPayload& data) { - ModelPointer model = data._model.lock(); - if (model && model->isLoaded()) { - // Ensure the model geometry was not reset between frames - if (deleteGeometryCounter == model->getGeometryCounter()) { - // this stuff identical to what happens in regular Model - const Model::MeshState& state = model->getMeshState(data._meshIndex); - Transform renderTransform = modelTransform; - if (state.clusterMatrices.size() == 1) { - renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); - } - data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer); + for (int i = 0; i < (int)self->_modelMeshRenderItemIDs.size(); i++) { - // this stuff for cauterized mesh - CauterizedModel* cModel = static_cast(model.get()); - const Model::MeshState& cState = cModel->getCauterizeMeshState(data._meshIndex); - renderTransform = modelTransform; - if (cState.clusterMatrices.size() == 1) { - renderTransform = modelTransform.worldTransform(Transform(cState.clusterMatrices[0])); - } - data.updateTransformForCauterizedMesh(renderTransform, cState.clusterBuffer); - } + auto itemID = self->_modelMeshRenderItemIDs[i]; + auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; + auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices); + auto clusterMatricesCauterized(self->getCauterizeMeshState(meshIndex).clusterMatrices); + + transaction.updateItem(itemID, [modelTransform, clusterMatrices, clusterMatricesCauterized](CauterizedMeshPartPayload& data) { + data.updateClusterBuffer(clusterMatrices, clusterMatricesCauterized); + + Transform renderTransform = modelTransform; + if (clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0])); } + data.updateTransformForSkinnedMesh(renderTransform, modelTransform); + + renderTransform = modelTransform; + if (clusterMatricesCauterized.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(clusterMatricesCauterized[0])); + } + data.updateTransformForCauterizedMesh(renderTransform); }); } diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index cc20f6335e..f70daf1e77 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -134,7 +134,7 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu color += directionalSpecular; // Attenuate the light if haze effect selected - if ((hazeParams.hazeMode & HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED) == HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED) { + if ((hazeParams.hazeMode & HAZE_MODE_IS_KEYLIGHT_ATTENUATED) == HAZE_MODE_IS_KEYLIGHT_ATTENUATED) { // Directional light attenuation is simulated by assuming the light source is at a fixed height above the // fragment. This height is where the haze density is reduced by 95% from the haze at the fragment's height // @@ -147,8 +147,8 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu // Height at which haze density is reduced by 95% (default set to 2000.0 for safety ,this should never happen) float height_95p = 2000.0; - if (hazeParams.hazeAltitudeFactorKeyLight > 0.0f) { - height_95p = -log(0.05) / hazeParams.hazeAltitudeFactorKeyLight; + if (hazeParams.hazeKeyLightAltitudeFactor > 0.0f) { + height_95p = -log(0.05) / hazeParams.hazeKeyLightAltitudeFactor; } // Note that the sine will always be positive @@ -168,8 +168,8 @@ vec3 evalSkyboxGlobalColor(mat4 invViewMat, float shadowAttenuation, float obscu // Integration is from the fragment towards the light source // Note that the haze base reference affects only the haze density as function of altitude float hazeDensityDistribution = - hazeParams.hazeRangeFactorKeyLight * - exp(-hazeParams.hazeAltitudeFactorKeyLight * (worldFragPos.y - hazeParams.hazeBaseReference)); + hazeParams.hazeKeyLightRangeFactor * + exp(-hazeParams.hazeKeyLightAltitudeFactor * (worldFragPos.y - hazeParams.hazeBaseReference)); float hazeIntegral = hazeDensityDistribution * distance; diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index bf254ce80e..f694a93033 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -21,32 +21,16 @@ #include "Haze_frag.h" -void HazeConfig::setHazeColorR(const float value) { - hazeColorR = value; +void HazeConfig::setHazeColor(const glm::vec3 value) { + hazeColor = value; } -void HazeConfig::setHazeColorG(const float value) { - hazeColorG = value; +void HazeConfig::setHazeGlareAngle(const float value) { + hazeGlareAngle = value; } -void HazeConfig::setHazeColorB(const float value) { - hazeColorB = value; -} - -void HazeConfig::setDirectionalLightAngle_degs(const float value) { - hazeDirectionalLightAngle_degs = value; -} - -void HazeConfig::setDirectionalLightColorR(const float value) { - hazeDirectionalLightColorR = value; -} - -void HazeConfig::setDirectionalLightColorG(const float value) { - hazeDirectionalLightColorG = value; -} - -void HazeConfig::setDirectionalLightColorB(const float value) { - hazeDirectionalLightColorB = value; +void HazeConfig::setHazeGlareColor(const glm::vec3 value) { + hazeGlareColor = value; } void HazeConfig::setHazeBaseReference(const float value) { @@ -73,24 +57,24 @@ void HazeConfig::setHazeEnableGlare(const bool active) { isHazeEnableGlare = active; } -void HazeConfig::setHazeRange_m(const float value) { - hazeRange_m = value; +void HazeConfig::setHazeRange(const float value) { + hazeRange = value; } -void HazeConfig::setHazeAltitude_m(const float value) { - hazeAltitude_m = value; +void HazeConfig::setHazeAltitude(const float value) { + hazeHeight = value; } -void HazeConfig::setHazeKeyLightRange_m(const float value) { - hazeKeyLightRange_m = value; +void HazeConfig::setHazeKeyLightRange(const float value) { + hazeKeyLightRange = value; } -void HazeConfig::setHazeKeyLightAltitude_m(const float value) { - hazeKeyLightAltitude_m = value; +void HazeConfig::setHazeKeyLightAltitude(const float value) { + hazeKeyLightAltitude = value; } -void HazeConfig::setHazeBackgroundBlendValue(const float value) { - hazeBackgroundBlendValue = value; +void HazeConfig::setHazeBackgroundBlend(const float value) { + hazeBackgroundBlend = value; } MakeHaze::MakeHaze() { @@ -98,10 +82,10 @@ MakeHaze::MakeHaze() { } void MakeHaze::configure(const Config& config) { - _haze->setHazeColor(glm::vec3(config.hazeColorR, config.hazeColorG, config.hazeColorB)); - _haze->setDirectionalLightBlend(model::convertDirectionalLightAngleToPower(config.hazeDirectionalLightAngle_degs)); + _haze->setHazeColor(config.hazeColor); + _haze->setHazeGlareBlend(model::Haze::convertGlareAngleToPower(config.hazeGlareAngle)); - _haze->setDirectionalLightColor(glm::vec3(config.hazeDirectionalLightColorR, config.hazeDirectionalLightColorG, config.hazeDirectionalLightColorB)); + _haze->setHazeGlareColor(config.hazeGlareColor); _haze->setHazeBaseReference(config.hazeBaseReference); _haze->setHazeActive(config.isHazeActive); @@ -110,13 +94,13 @@ void MakeHaze::configure(const Config& config) { _haze->setModulateColorActive(config.isModulateColorActive); _haze->setHazeEnableGlare(config.isHazeEnableGlare); - _haze->setHazeRangeFactor(model::convertHazeRangeToHazeRangeFactor(config.hazeRange_m)); - _haze->setHazeAltitudeFactor(model::convertHazeAltitudeToHazeAltitudeFactor(config.hazeAltitude_m)); + _haze->setHazeRangeFactor(model::Haze::convertHazeRangeToHazeRangeFactor(config.hazeRange)); + _haze->setHazeAltitudeFactor(model::Haze::convertHazeAltitudeToHazeAltitudeFactor(config.hazeHeight)); - _haze->setHazeKeyLightRangeFactor(model::convertHazeRangeToHazeRangeFactor(config.hazeKeyLightRange_m)); - _haze->setHazeKeyLightAltitudeFactor(model::convertHazeAltitudeToHazeAltitudeFactor(config.hazeKeyLightAltitude_m)); + _haze->setHazeKeyLightRangeFactor(model::Haze::convertHazeRangeToHazeRangeFactor(config.hazeKeyLightRange)); + _haze->setHazeKeyLightAltitudeFactor(model::Haze::convertHazeAltitudeToHazeAltitudeFactor(config.hazeKeyLightAltitude)); - _haze->setHazeBackgroundBlendValue(config.hazeBackgroundBlendValue); + _haze->setHazeBackgroundBlend(config.hazeBackgroundBlend); } void MakeHaze::run(const render::RenderContextPointer& renderContext, model::HazePointer& haze) { @@ -156,7 +140,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Mask out haze on the tablet - PrepareStencil::testNoAA(*state); + PrepareStencil::testMask(*state); gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), HazeEffect_ParamsSlot)); diff --git a/libraries/render-utils/src/DrawHaze.h b/libraries/render-utils/src/DrawHaze.h index 4a7b2135bd..f158daa0c6 100644 --- a/libraries/render-utils/src/DrawHaze.h +++ b/libraries/render-utils/src/DrawHaze.h @@ -19,24 +19,19 @@ #include #include #include +#include #include "SurfaceGeometryPass.h" -#include "model/Haze.h" - using LinearDepthFramebufferPointer = std::shared_ptr; class MakeHazeConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(float hazeColorR MEMBER hazeColorR WRITE setHazeColorR NOTIFY dirty); - Q_PROPERTY(float hazeColorG MEMBER hazeColorG WRITE setHazeColorG NOTIFY dirty); - Q_PROPERTY(float hazeColorB MEMBER hazeColorB WRITE setHazeColorB NOTIFY dirty); - Q_PROPERTY(float hazeDirectionalLightAngle_degs MEMBER hazeDirectionalLightAngle_degs WRITE setDirectionalLightAngle_degs NOTIFY dirty); + Q_PROPERTY(glm::vec3 hazeColor MEMBER hazeColor WRITE setHazeColor NOTIFY dirty); + Q_PROPERTY(float hazeGlareAngle MEMBER hazeGlareAngle WRITE setHazeGlareAngle NOTIFY dirty); - Q_PROPERTY(float hazeDirectionalLightColorR MEMBER hazeDirectionalLightColorR WRITE setDirectionalLightColorR NOTIFY dirty); - Q_PROPERTY(float hazeDirectionalLightColorG MEMBER hazeDirectionalLightColorG WRITE setDirectionalLightColorG NOTIFY dirty); - Q_PROPERTY(float hazeDirectionalLightColorB MEMBER hazeDirectionalLightColorB WRITE setDirectionalLightColorB NOTIFY dirty); + Q_PROPERTY(glm::vec3 hazeGlareColor MEMBER hazeGlareColor WRITE setHazeGlareColor NOTIFY dirty); Q_PROPERTY(float hazeBaseReference MEMBER hazeBaseReference WRITE setHazeBaseReference NOTIFY dirty); Q_PROPERTY(bool isHazeActive MEMBER isHazeActive WRITE setHazeActive NOTIFY dirty); @@ -45,26 +40,22 @@ class MakeHazeConfig : public render::Job::Config { Q_PROPERTY(bool isModulateColorActive MEMBER isModulateColorActive WRITE setModulateColorActive NOTIFY dirty); Q_PROPERTY(bool isHazeEnableGlare MEMBER isHazeEnableGlare WRITE setHazeEnableGlare NOTIFY dirty); - Q_PROPERTY(float hazeRange_m MEMBER hazeRange_m WRITE setHazeRange_m NOTIFY dirty); - Q_PROPERTY(float hazeAltitude_m MEMBER hazeAltitude_m WRITE setHazeAltitude_m NOTIFY dirty); + Q_PROPERTY(float hazeRange MEMBER hazeRange WRITE setHazeRange NOTIFY dirty); + Q_PROPERTY(float hazeHeight MEMBER hazeHeight WRITE setHazeAltitude NOTIFY dirty); - Q_PROPERTY(float hazeKeyLightRange_m MEMBER hazeKeyLightRange_m WRITE setHazeKeyLightRange_m NOTIFY dirty); - Q_PROPERTY(float hazeKeyLightAltitude_m MEMBER hazeKeyLightAltitude_m WRITE setHazeKeyLightAltitude_m NOTIFY dirty); + Q_PROPERTY(float hazeKeyLightRange MEMBER hazeKeyLightRange WRITE setHazeKeyLightRange NOTIFY dirty); + Q_PROPERTY(float hazeKeyLightAltitude MEMBER hazeKeyLightAltitude WRITE setHazeKeyLightAltitude NOTIFY dirty); - Q_PROPERTY(float hazeBackgroundBlendValue MEMBER hazeBackgroundBlendValue WRITE setHazeBackgroundBlendValue NOTIFY dirty); + Q_PROPERTY(float hazeBackgroundBlend MEMBER hazeBackgroundBlend WRITE setHazeBackgroundBlend NOTIFY dirty); public: MakeHazeConfig() : render::Job::Config() {} - float hazeColorR{ model::initialHazeColor.r }; - float hazeColorG{ model::initialHazeColor.g }; - float hazeColorB{ model::initialHazeColor.b }; - float hazeDirectionalLightAngle_degs{ model::initialDirectionalLightAngle_degs }; + glm::vec3 hazeColor{ model::Haze::INITIAL_HAZE_COLOR }; + float hazeGlareAngle{ model::Haze::INITIAL_HAZE_GLARE_ANGLE }; - float hazeDirectionalLightColorR{ model::initialDirectionalLightColor.r }; - float hazeDirectionalLightColorG{ model::initialDirectionalLightColor.g }; - float hazeDirectionalLightColorB{ model::initialDirectionalLightColor.b }; - float hazeBaseReference{ model::initialHazeBaseReference }; + glm::vec3 hazeGlareColor{ model::Haze::INITIAL_HAZE_GLARE_COLOR }; + float hazeBaseReference{ model::Haze::INITIAL_HAZE_BASE_REFERENCE }; bool isHazeActive{ false }; bool isAltitudeBased{ false }; @@ -72,23 +63,19 @@ public: bool isModulateColorActive{ false }; bool isHazeEnableGlare{ false }; - float hazeRange_m{ model::initialHazeRange_m }; - float hazeAltitude_m{ model::initialHazeAltitude_m }; + float hazeRange{ model::Haze::INITIAL_HAZE_RANGE }; + float hazeHeight{ model::Haze::INITIAL_HAZE_HEIGHT }; - float hazeKeyLightRange_m{ model::initialHazeKeyLightRange_m }; - float hazeKeyLightAltitude_m{ model::initialHazeKeyLightAltitude_m }; + float hazeKeyLightRange{ model::Haze::INITIAL_KEY_LIGHT_RANGE }; + float hazeKeyLightAltitude{ model::Haze::INITIAL_KEY_LIGHT_ALTITUDE }; - float hazeBackgroundBlendValue{ model::initialHazeBackgroundBlendValue }; + float hazeBackgroundBlend{ model::Haze::INITIAL_HAZE_BACKGROUND_BLEND }; public slots: - void setHazeColorR(const float value) { hazeColorR = value; emit dirty(); } - void setHazeColorG(const float value) { hazeColorG = value; emit dirty(); } - void setHazeColorB(const float value) { hazeColorB = value; emit dirty(); } - void setDirectionalLightAngle_degs(const float value) { hazeDirectionalLightAngle_degs = value; emit dirty(); } + void setHazeColor(const glm::vec3 value) { hazeColor = value; emit dirty(); } + void setHazeGlareAngle(const float value) { hazeGlareAngle = value; emit dirty(); } - void setDirectionalLightColorR(const float value) { hazeDirectionalLightColorR = value; emit dirty(); } - void setDirectionalLightColorG(const float value) { hazeDirectionalLightColorG = value; emit dirty(); } - void setDirectionalLightColorB(const float value) { hazeDirectionalLightColorB = value; emit dirty(); } + void setHazeGlareColor(const glm::vec3 value) { hazeGlareColor = value; emit dirty(); } void setHazeBaseReference(const float value) { hazeBaseReference = value; ; emit dirty(); } void setHazeActive(const bool active) { isHazeActive = active; emit dirty(); } @@ -97,13 +84,13 @@ public slots: void setModulateColorActive(const bool active) { isModulateColorActive = active; emit dirty(); } void setHazeEnableGlare(const bool active) { isHazeEnableGlare = active; emit dirty(); } - void setHazeRange_m(const float value) { hazeRange_m = value; emit dirty(); } - void setHazeAltitude_m(const float value) { hazeAltitude_m = value; emit dirty(); } + void setHazeRange(const float value) { hazeRange = value; emit dirty(); } + void setHazeAltitude(const float value) { hazeHeight = value; emit dirty(); } - void setHazeKeyLightRange_m(const float value) { hazeKeyLightRange_m = value; emit dirty(); } - void setHazeKeyLightAltitude_m(const float value) { hazeKeyLightAltitude_m = value; emit dirty(); } + void setHazeKeyLightRange(const float value) { hazeKeyLightRange = value; emit dirty(); } + void setHazeKeyLightAltitude(const float value) { hazeKeyLightAltitude = value; emit dirty(); } - void setHazeBackgroundBlendValue(const float value) { hazeBackgroundBlendValue = value; ; emit dirty(); } + void setHazeBackgroundBlend(const float value) { hazeBackgroundBlend = value; ; emit dirty(); } signals: void dirty(); @@ -128,15 +115,11 @@ public: HazeConfig() : render::Job::Config(true) {} // attributes - float hazeColorR{ model::initialHazeColor.r }; - float hazeColorG{ model::initialHazeColor.g }; - float hazeColorB{ model::initialHazeColor.b }; - float hazeDirectionalLightAngle_degs{ model::initialDirectionalLightAngle_degs }; + glm::vec3 hazeColor{ model::Haze::INITIAL_HAZE_COLOR }; + float hazeGlareAngle{ model::Haze::INITIAL_HAZE_GLARE_ANGLE }; - float hazeDirectionalLightColorR{ model::initialDirectionalLightColor.r }; - float hazeDirectionalLightColorG{ model::initialDirectionalLightColor.g }; - float hazeDirectionalLightColorB{ model::initialDirectionalLightColor.b }; - float hazeBaseReference{ model::initialHazeBaseReference }; + glm::vec3 hazeGlareColor{ model::Haze::INITIAL_HAZE_GLARE_COLOR }; + float hazeBaseReference{ model::Haze::INITIAL_HAZE_BASE_REFERENCE }; bool isHazeActive{ false }; // Setting this to true will set haze to on bool isAltitudeBased{ false }; @@ -144,23 +127,19 @@ public: bool isModulateColorActive{ false }; bool isHazeEnableGlare{ false }; - float hazeRange_m{ model::initialHazeRange_m }; - float hazeAltitude_m{ model::initialHazeAltitude_m }; + float hazeRange{ model::Haze::INITIAL_HAZE_RANGE }; + float hazeHeight{ model::Haze::INITIAL_HAZE_HEIGHT }; - float hazeKeyLightRange_m{ model::initialHazeKeyLightRange_m }; - float hazeKeyLightAltitude_m{ model::initialHazeKeyLightAltitude_m }; + float hazeKeyLightRange{ model::Haze::INITIAL_KEY_LIGHT_RANGE }; + float hazeKeyLightAltitude{ model::Haze::INITIAL_KEY_LIGHT_ALTITUDE }; - float hazeBackgroundBlendValue{ model::initialHazeBackgroundBlendValue }; + float hazeBackgroundBlend{ model::Haze::INITIAL_HAZE_BACKGROUND_BLEND }; // methods - void setHazeColorR(const float value); - void setHazeColorG(const float value); - void setHazeColorB(const float value); - void setDirectionalLightAngle_degs(const float value); + void setHazeColor(const glm::vec3 value); + void setHazeGlareAngle(const float value); - void setDirectionalLightColorR(const float value); - void setDirectionalLightColorG(const float value); - void setDirectionalLightColorB(const float value); + void setHazeGlareColor(const glm::vec3 value); void setHazeBaseReference(const float value); void setHazeActive(const bool active); @@ -169,13 +148,13 @@ public: void setModulateColorActive(const bool active); void setHazeEnableGlare(const bool active); - void setHazeRange_m(const float value); - void setHazeAltitude_m(const float value); + void setHazeRange(const float value); + void setHazeAltitude(const float value); - void setHazeKeyLightRange_m(const float value); - void setHazeKeyLightAltitude_m(const float value); + void setHazeKeyLightRange(const float value); + void setHazeKeyLightAltitude(const float value); - void setHazeBackgroundBlendValue(const float value); + void setHazeBackgroundBlend(const float value); }; class DrawHaze { diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index f35fb9f830..fa00737e3c 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -482,8 +482,10 @@ void GeometryCache::buildShapes() { using namespace geometry; auto vertexBuffer = std::make_shared(); auto indexBuffer = std::make_shared(); - // Cube + // Cube setupFlatShape(_shapes[Cube], geometry::cube(), _shapeVertices, _shapeIndices); + //Quad renders as flat Cube + setupFlatShape(_shapes[Quad], geometry::cube(), _shapeVertices, _shapeIndices); // Tetrahedron setupFlatShape(_shapes[Tetrahedron], geometry::tetrahedron(), _shapeVertices, _shapeIndices); // Icosahedron @@ -524,12 +526,10 @@ void GeometryCache::buildShapes() { extrudePolygon<64>(_shapes[Cylinder], _shapeVertices, _shapeIndices); //Cone, extrudePolygon<64>(_shapes[Cone], _shapeVertices, _shapeIndices, true); - //Circle - drawCircle(_shapes[Circle], _shapeVertices, _shapeIndices); + // Circle renders as flat Cylinder + extrudePolygon<64>(_shapes[Circle], _shapeVertices, _shapeIndices); // Not implemented yet: - //Quad, - //Torus, - + //Torus, } const GeometryCache::ShapeData * GeometryCache::getShapeData(const Shape shape) const { diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index 77c820e093..b366e6d639 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -60,15 +60,15 @@ void main(void) { Light light = getLight(); vec3 lightDirection = getLightDirection(light); - float directionalLightComponent = max(0.0, dot(eyeFragDir, -lightDirection)); - float power = min(1.0, pow(directionalLightComponent, hazeParams.directionalLightBlend)); + float glareComponent = max(0.0, dot(eyeFragDir, -lightDirection)); + float power = min(1.0, pow(glareComponent, hazeParams.hazeGlareBlend)); - vec4 directionalLightColor = vec4(hazeParams.directionalLightColor, 1.0); + vec4 glareColor = vec4(hazeParams.hazeGlareColor, 1.0); - // Use the haze colour for the belnd-out colour, if blend is not enabled + // Use the haze colour for the glare colour, if blend is not enabled vec4 blendedHazeColor; if ((hazeParams.hazeMode & HAZE_MODE_IS_ENABLE_LIGHT_BLEND) == HAZE_MODE_IS_ENABLE_LIGHT_BLEND) { - blendedHazeColor = mix(hazeColor, directionalLightColor, power); + blendedHazeColor = mix(hazeColor, glareColor, power); } else { blendedHazeColor = hazeColor; } @@ -86,14 +86,14 @@ void main(void) { // Note that the haze base reference affects only the haze density as function of altitude vec3 hazeDensityDistribution = hazeParams.colorModulationFactor * - exp(-hazeParams.hazeAltitudeFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); + exp(-hazeParams.hazeHeightFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); vec3 hazeIntegral = hazeDensityDistribution * distance; const float slopeThreshold = 0.01; float deltaHeight = worldFragPos.y - worldEyePos.y; if (abs(deltaHeight) > slopeThreshold) { - float t = hazeParams.hazeAltitudeFactor * deltaHeight; + float t = hazeParams.hazeHeightFactor * deltaHeight; hazeIntegral *= (1.0 - exp (-t)) / t; } @@ -117,14 +117,14 @@ void main(void) { // Note that the haze base reference affects only the haze density as function of altitude float hazeDensityDistribution = hazeParams.hazeRangeFactor * - exp(-hazeParams.hazeAltitudeFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); + exp(-hazeParams.hazeHeightFactor * (worldEyePos.y - hazeParams.hazeBaseReference)); float hazeIntegral = hazeDensityDistribution * distance; const float slopeThreshold = 0.01; float deltaHeight = worldFragPos.y - worldEyePos.y; if (abs(deltaHeight) > slopeThreshold) { - float t = hazeParams.hazeAltitudeFactor * deltaHeight; + float t = hazeParams.hazeHeightFactor * deltaHeight; // Protect from wild values if (abs(t) > 0.0000001) { hazeIntegral *= (1.0 - exp (-t)) / t; @@ -140,7 +140,7 @@ void main(void) { // Mix with background at far range const float BLEND_DISTANCE = 27000.0; if (distance > BLEND_DISTANCE) { - outFragColor = mix(potentialFragColor, fragColor, hazeParams.backgroundBlendValue); + outFragColor = mix(potentialFragColor, fragColor, hazeParams.backgroundBlend); } else { outFragColor = potentialFragColor; } diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index 614431dce7..de7f0ac246 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -12,28 +12,28 @@ const int HAZE_MODE_IS_ACTIVE = 1 << 0; const int HAZE_MODE_IS_ALTITUDE_BASED = 1 << 1; -const int HAZE_MODE_IS_DIRECTIONAL_LIGHT_ATTENUATED = 1 << 2; +const int HAZE_MODE_IS_KEYLIGHT_ATTENUATED = 1 << 2; const int HAZE_MODE_IS_MODULATE_COLOR = 1 << 3; const int HAZE_MODE_IS_ENABLE_LIGHT_BLEND = 1 << 4; struct HazeParams { vec3 hazeColor; - float directionalLightBlend; + float hazeGlareBlend; - vec3 directionalLightColor; + vec3 hazeGlareColor; float hazeBaseReference; vec3 colorModulationFactor; int hazeMode; mat4 zoneTransform; - float backgroundBlendValue; + float backgroundBlend; float hazeRangeFactor; - float hazeAltitudeFactor; + float hazeHeightFactor; - float hazeRangeFactorKeyLight; - float hazeAltitudeFactorKeyLight; + float hazeKeyLightRangeFactor; + float hazeKeyLightAltitudeFactor; }; layout(std140) uniform hazeBuffer { diff --git a/libraries/render-utils/src/HazeStage.cpp b/libraries/render-utils/src/HazeStage.cpp index aa7a7f554c..016282d16f 100644 --- a/libraries/render-utils/src/HazeStage.cpp +++ b/libraries/render-utils/src/HazeStage.cpp @@ -14,16 +14,17 @@ #include std::string HazeStage::_stageName { "HAZE_STAGE"}; +const HazeStage::Index HazeStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; FetchHazeStage::FetchHazeStage() { _haze = std::make_shared(); } void FetchHazeStage::configure(const Config& config) { - _haze->setHazeColor(glm::vec3(config.hazeColorR, config.hazeColorG, config.hazeColorB)); - _haze->setDirectionalLightBlend(model::convertDirectionalLightAngleToPower(config.hazeDirectionalLightAngle_degs)); + _haze->setHazeColor(config.hazeColor); + _haze->setHazeGlareBlend(model::Haze::convertGlareAngleToPower(config.hazeGlareAngle)); - _haze->setDirectionalLightColor(glm::vec3(config.hazeDirectionalLightColorR, config.hazeDirectionalLightColorG, config.hazeDirectionalLightColorB)); + _haze->setHazeGlareColor(config.hazeGlareColor); _haze->setHazeBaseReference(config.hazeBaseReference); _haze->setHazeActive(config.isHazeActive); @@ -32,13 +33,13 @@ void FetchHazeStage::configure(const Config& config) { _haze->setModulateColorActive(config.isModulateColorActive); _haze->setHazeEnableGlare(config.isHazeEnableGlare); - _haze->setHazeRangeFactor(model::convertHazeRangeToHazeRangeFactor(config.hazeRange_m)); - _haze->setHazeAltitudeFactor(model::convertHazeAltitudeToHazeAltitudeFactor(config.hazeAltitude_m)); + _haze->setHazeRangeFactor(model::Haze::convertHazeRangeToHazeRangeFactor(config.hazeRange)); + _haze->setHazeAltitudeFactor(model::Haze::convertHazeAltitudeToHazeAltitudeFactor(config.hazeHeight)); - _haze->setHazeKeyLightRangeFactor(model::convertHazeRangeToHazeRangeFactor(config.hazeKeyLightRange_m)); - _haze->setHazeKeyLightAltitudeFactor(model::convertHazeAltitudeToHazeAltitudeFactor(config.hazeKeyLightAltitude_m)); + _haze->setHazeKeyLightRangeFactor(model::Haze::convertHazeRangeToHazeRangeFactor(config.hazeKeyLightRange)); + _haze->setHazeKeyLightAltitudeFactor(model::Haze::convertHazeAltitudeToHazeAltitudeFactor(config.hazeKeyLightAltitude)); - _haze->setHazeBackgroundBlendValue(config.hazeBackgroundBlendValue); + _haze->setHazeBackgroundBlend(config.hazeBackgroundBlend); } HazeStage::Index HazeStage::findHaze(const HazePointer& haze) const { diff --git a/libraries/render-utils/src/HazeStage.h b/libraries/render-utils/src/HazeStage.h index 7cc0c659b0..c355f06644 100644 --- a/libraries/render-utils/src/HazeStage.h +++ b/libraries/render-utils/src/HazeStage.h @@ -19,7 +19,7 @@ #include #include -#include "model/Haze.h" +#include // Haze stage to set up haze-related rendering tasks class HazeStage : public render::Stage { @@ -28,7 +28,7 @@ public: static const std::string& getName() { return _stageName; } using Index = render::indexed_container::Index; - static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX }; + static const Index INVALID_INDEX; static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } using HazePointer = model::HazePointer; @@ -83,14 +83,10 @@ protected: class FetchHazeConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(float hazeColorR MEMBER hazeColorR WRITE setHazeColorR NOTIFY dirty); - Q_PROPERTY(float hazeColorG MEMBER hazeColorG WRITE setHazeColorG NOTIFY dirty); - Q_PROPERTY(float hazeColorB MEMBER hazeColorB WRITE setHazeColorB NOTIFY dirty); - Q_PROPERTY(float hazeDirectionalLightAngle_degs MEMBER hazeDirectionalLightAngle_degs WRITE setDirectionalLightAngle_degs NOTIFY dirty); + Q_PROPERTY(glm::vec3 hazeColor MEMBER hazeColor WRITE setHazeColor NOTIFY dirty); + Q_PROPERTY(float hazeGlareAngle MEMBER hazeGlareAngle WRITE setHazeGlareAngle NOTIFY dirty); - Q_PROPERTY(float hazeDirectionalLightColorR MEMBER hazeDirectionalLightColorR WRITE setDirectionalLightColorR NOTIFY dirty); - Q_PROPERTY(float hazeDirectionalLightColorG MEMBER hazeDirectionalLightColorG WRITE setDirectionalLightColorG NOTIFY dirty); - Q_PROPERTY(float hazeDirectionalLightColorB MEMBER hazeDirectionalLightColorB WRITE setDirectionalLightColorB NOTIFY dirty); + Q_PROPERTY(glm::vec3 hazeGlareColor MEMBER hazeGlareColor WRITE setHazeGlareColor NOTIFY dirty); Q_PROPERTY(float hazeBaseReference MEMBER hazeBaseReference WRITE setHazeBaseReference NOTIFY dirty); Q_PROPERTY(bool isHazeActive MEMBER isHazeActive WRITE setHazeActive NOTIFY dirty); @@ -99,26 +95,22 @@ class FetchHazeConfig : public render::Job::Config { Q_PROPERTY(bool isModulateColorActive MEMBER isModulateColorActive WRITE setModulateColorActive NOTIFY dirty); Q_PROPERTY(bool isHazeEnableGlare MEMBER isHazeEnableGlare WRITE setHazeEnableGlare NOTIFY dirty); - Q_PROPERTY(float hazeRange_m MEMBER hazeRange_m WRITE setHazeRange_m NOTIFY dirty); - Q_PROPERTY(float hazeAltitude_m MEMBER hazeAltitude_m WRITE setHazeAltitude_m NOTIFY dirty); + Q_PROPERTY(float hazeRange MEMBER hazeRange WRITE setHazeRange NOTIFY dirty); + Q_PROPERTY(float hazeHeight MEMBER hazeHeight WRITE setHazeAltitude NOTIFY dirty); - Q_PROPERTY(float hazeKeyLightRange_m MEMBER hazeKeyLightRange_m WRITE setHazeKeyLightRange_m NOTIFY dirty); - Q_PROPERTY(float hazeKeyLightAltitude_m MEMBER hazeKeyLightAltitude_m WRITE setHazeKeyLightAltitude_m NOTIFY dirty); + Q_PROPERTY(float hazeKeyLightRange MEMBER hazeKeyLightRange WRITE setHazeKeyLightRange NOTIFY dirty); + Q_PROPERTY(float hazeKeyLightAltitude MEMBER hazeKeyLightAltitude WRITE setHazeKeyLightAltitude NOTIFY dirty); - Q_PROPERTY(float hazeBackgroundBlendValue MEMBER hazeBackgroundBlendValue WRITE setHazeBackgroundBlendValue NOTIFY dirty); + Q_PROPERTY(float hazeBackgroundBlend MEMBER hazeBackgroundBlend WRITE setHazeBackgroundBlend NOTIFY dirty); public: FetchHazeConfig() : render::Job::Config() {} - float hazeColorR{ model::initialHazeColor.r }; - float hazeColorG{ model::initialHazeColor.g }; - float hazeColorB{ model::initialHazeColor.b }; - float hazeDirectionalLightAngle_degs{ model::initialDirectionalLightAngle_degs }; + glm::vec3 hazeColor{ model::Haze::INITIAL_HAZE_COLOR }; + float hazeGlareAngle{ model::Haze::INITIAL_HAZE_GLARE_ANGLE }; - float hazeDirectionalLightColorR{ model::initialDirectionalLightColor.r }; - float hazeDirectionalLightColorG{ model::initialDirectionalLightColor.g }; - float hazeDirectionalLightColorB{ model::initialDirectionalLightColor.b }; - float hazeBaseReference{ model::initialHazeBaseReference }; + glm::vec3 hazeGlareColor{ model::Haze::INITIAL_HAZE_GLARE_COLOR }; + float hazeBaseReference{ model::Haze::INITIAL_HAZE_BASE_REFERENCE }; bool isHazeActive{ false }; bool isAltitudeBased{ false }; @@ -126,23 +118,19 @@ public: bool isModulateColorActive{ false }; bool isHazeEnableGlare{ false }; - float hazeRange_m{ model::initialHazeRange_m }; - float hazeAltitude_m{ model::initialHazeAltitude_m }; + float hazeRange{ model::Haze::INITIAL_HAZE_RANGE }; + float hazeHeight{ model::Haze::INITIAL_HAZE_HEIGHT }; - float hazeKeyLightRange_m{ model::initialHazeKeyLightRange_m }; - float hazeKeyLightAltitude_m{ model::initialHazeKeyLightAltitude_m }; + float hazeKeyLightRange{ model::Haze::INITIAL_KEY_LIGHT_RANGE }; + float hazeKeyLightAltitude{ model::Haze::INITIAL_KEY_LIGHT_ALTITUDE }; - float hazeBackgroundBlendValue{ model::initialHazeBackgroundBlendValue }; + float hazeBackgroundBlend{ model::Haze::INITIAL_HAZE_BACKGROUND_BLEND }; public slots: - void setHazeColorR(const float value) { hazeColorR = value; emit dirty(); } - void setHazeColorG(const float value) { hazeColorG = value; emit dirty(); } - void setHazeColorB(const float value) { hazeColorB = value; emit dirty(); } - void setDirectionalLightAngle_degs(const float value) { hazeDirectionalLightAngle_degs = value; emit dirty(); } + void setHazeColor(const glm::vec3 value) { hazeColor = value; emit dirty(); } + void setHazeGlareAngle(const float value) { hazeGlareAngle = value; emit dirty(); } - void setDirectionalLightColorR(const float value) { hazeDirectionalLightColorR = value; emit dirty(); } - void setDirectionalLightColorG(const float value) { hazeDirectionalLightColorG = value; emit dirty(); } - void setDirectionalLightColorB(const float value) { hazeDirectionalLightColorB = value; emit dirty(); } + void setHazeGlareColor(const glm::vec3 value) { hazeGlareColor = value; emit dirty(); } void setHazeBaseReference(const float value) { hazeBaseReference = value; ; emit dirty(); } void setHazeActive(const bool active) { isHazeActive = active; emit dirty(); } @@ -151,13 +139,13 @@ public slots: void setModulateColorActive(const bool active) { isModulateColorActive = active; emit dirty(); } void setHazeEnableGlare(const bool active) { isHazeEnableGlare = active; emit dirty(); } - void setHazeRange_m(const float value) { hazeRange_m = value; emit dirty(); } - void setHazeAltitude_m(const float value) { hazeAltitude_m = value; emit dirty(); } + void setHazeRange(const float value) { hazeRange = value; emit dirty(); } + void setHazeAltitude(const float value) { hazeHeight = value; emit dirty(); } - void setHazeKeyLightRange_m(const float value) { hazeKeyLightRange_m = value; emit dirty(); } - void setHazeKeyLightAltitude_m(const float value) { hazeKeyLightAltitude_m = value; emit dirty(); } + void setHazeKeyLightRange(const float value) { hazeKeyLightRange = value; emit dirty(); } + void setHazeKeyLightAltitude(const float value) { hazeKeyLightAltitude = value; emit dirty(); } - void setHazeBackgroundBlendValue(const float value) { hazeBackgroundBlendValue = value; ; emit dirty(); } + void setHazeBackgroundBlend(const float value) { hazeBackgroundBlend = value; ; emit dirty(); } signals: void dirty(); diff --git a/libraries/render-utils/src/Outline.slf b/libraries/render-utils/src/Highlight.slf similarity index 72% rename from libraries/render-utils/src/Outline.slf rename to libraries/render-utils/src/Highlight.slf index 68ef870cba..bf65f92613 100644 --- a/libraries/render-utils/src/Outline.slf +++ b/libraries/render-utils/src/Highlight.slf @@ -1,5 +1,5 @@ -// Outline.slf -// Add outline effect based on two zbuffers : one containing the total scene z and another +// Highlight.slf +// Add highlight effect based on two zbuffers : one containing the total scene z and another // with the z of only the objects to be outlined. // This is the version without the fill effect inside the silhouette. // @@ -9,5 +9,5 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include Outline.slh@> +<@include Highlight.slh@> <$main(0)$> diff --git a/libraries/render-utils/src/Outline.slh b/libraries/render-utils/src/Highlight.slh similarity index 67% rename from libraries/render-utils/src/Outline.slh rename to libraries/render-utils/src/Highlight.slh index ac56e4c95c..2faa10682e 100644 --- a/libraries/render-utils/src/Outline.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -1,7 +1,7 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> <$declareDeferredFrameTransform()$> -<@include Outline_shared.slh@> +<@include Highlight_shared.slh@> -uniform outlineParamsBuffer { - OutlineParameters params; +uniform highlightParamsBuffer { + HighlightParameters params; }; uniform sampler2D sceneDepthMap; -uniform sampler2D outlinedDepthMap; +uniform sampler2D highlightedDepthMap; in vec2 varTexCoord0; out vec4 outFragColor; @@ -35,30 +35,26 @@ void main(void) { // We offset by half a texel to be centered on the depth sample. If we don't do this // the blur will have a different width between the left / right sides and top / bottom // sides of the silhouette - vec2 halfTexel = getInvWidthHeight() / 2; - vec2 texCoord0 = varTexCoord0+halfTexel; - float outlinedDepth = texture(outlinedDepthMap, texCoord0).x; + float highlightedDepth = texture(highlightedDepthMap, varTexCoord0).x; float intensity = 0.0; - if (outlinedDepth < FAR_Z) { - // We're not on the far plane so we are on the outlined object, thus no outline to do! + if (highlightedDepth < FAR_Z) { + // We're not on the far plane so we are on the highlighted object, thus no outline to do! <@if IS_FILLED@> // But we need to fill the interior - float sceneDepth = texture(sceneDepthMap, texCoord0).x; + float sceneDepth = texture(sceneDepthMap, varTexCoord0).x; // Transform to linear depth for better precision - outlinedDepth = -evalZeyeFromZdb(outlinedDepth); + highlightedDepth = -evalZeyeFromZdb(highlightedDepth); sceneDepth = -evalZeyeFromZdb(sceneDepth); // Are we occluded? - if (sceneDepth < (outlinedDepth-LINEAR_DEPTH_BIAS)) { - intensity = params._fillOpacityOccluded; - } else { - intensity = params._fillOpacityUnoccluded; - } + intensity = sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS) ? params._occludedFillOpacity : params._unoccludedFillOpacity; <@else@> discard; <@endif@> } else { + vec2 halfTexel = getInvWidthHeight() / 2; + vec2 texCoord0 = varTexCoord0+halfTexel; float weight = 0.0; vec2 deltaUv = params._size / params._blurKernelSize; vec2 lineStartUv = texCoord0 - params._size / 2.0; @@ -74,9 +70,9 @@ void main(void) { for (x=0 ; x=0.0 && uv.x<=1.0) { - outlinedDepth = texture(outlinedDepthMap, uv).x; - intensity += (outlinedDepth < FAR_Z) ? 1.0 : 0.0; - weight += 1.f; + highlightedDepth = texture(highlightedDepthMap, uv).x; + intensity += (highlightedDepth < FAR_Z) ? 1.0 : 0.0; + weight += 1.0; } uv.x += deltaUv.x; } diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp new file mode 100644 index 0000000000..7c58e5ba66 --- /dev/null +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -0,0 +1,562 @@ +// +// HighlightEffect.cpp +// render-utils/src/ +// +// Created by Olivier Prat on 08/08/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 "HighlightEffect.h" + +#include "GeometryCache.h" + +#include "CubeProjectedPolygon.h" + +#include +#include + +#include "gpu/Context.h" +#include "gpu/StandardShaderLib.h" + +#include + +#include "surfaceGeometry_copyDepth_frag.h" +#include "debug_deferred_buffer_vert.h" +#include "debug_deferred_buffer_frag.h" +#include "Highlight_frag.h" +#include "Highlight_filled_frag.h" +#include "Highlight_aabox_vert.h" +#include "nop_frag.h" + +using namespace render; + +#define OUTLINE_STENCIL_MASK 1 + +HighlightRessources::HighlightRessources() { +} + +void HighlightRessources::update(const gpu::FramebufferPointer& primaryFrameBuffer) { + auto newFrameSize = glm::ivec2(primaryFrameBuffer->getSize()); + + // If the buffer size changed, we need to delete our FBOs and recreate them at the + // new correct dimensions. + if (_frameSize != newFrameSize) { + _frameSize = newFrameSize; + allocateDepthBuffer(primaryFrameBuffer); + allocateColorBuffer(primaryFrameBuffer); + } else { + if (!_depthFrameBuffer) { + allocateDepthBuffer(primaryFrameBuffer); + } + if (!_colorFrameBuffer) { + allocateColorBuffer(primaryFrameBuffer); + } + } +} + +void HighlightRessources::allocateColorBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) { + _colorFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithStencil")); + _colorFrameBuffer->setRenderBuffer(0, primaryFrameBuffer->getRenderBuffer(0)); + _colorFrameBuffer->setStencilBuffer(_depthStencilTexture, _depthStencilTexture->getTexelFormat()); +} + +void HighlightRessources::allocateDepthBuffer(const gpu::FramebufferPointer& primaryFrameBuffer) { + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); + _depthStencilTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(depthFormat, _frameSize.x, _frameSize.y)); + _depthFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("highlightDepth")); + _depthFrameBuffer->setDepthStencilBuffer(_depthStencilTexture, depthFormat); +} + +gpu::FramebufferPointer HighlightRessources::getDepthFramebuffer() { + assert(_depthFrameBuffer); + return _depthFrameBuffer; +} + +gpu::TexturePointer HighlightRessources::getDepthTexture() { + return _depthStencilTexture; +} + +gpu::FramebufferPointer HighlightRessources::getColorFramebuffer() { + assert(_colorFrameBuffer); + return _colorFrameBuffer; +} + +HighlightSharedParameters::HighlightSharedParameters() { + _highlightIds.fill(render::HighlightStage::INVALID_INDEX); +} + +float HighlightSharedParameters::getBlurPixelWidth(const render::HighlightStyle& style, int frameBufferHeight) { + return ceilf(style.outlineWidth * frameBufferHeight / 400.0f); +} + +PrepareDrawHighlight::PrepareDrawHighlight() { + _ressources = std::make_shared(); +} + +void PrepareDrawHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + auto destinationFrameBuffer = inputs; + + _ressources->update(destinationFrameBuffer); + outputs = _ressources; +} + +gpu::PipelinePointer DrawHighlightMask::_stencilMaskPipeline; +gpu::PipelinePointer DrawHighlightMask::_stencilMaskFillPipeline; + +DrawHighlightMask::DrawHighlightMask(unsigned int highlightIndex, + render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters) : + _highlightPassIndex{ highlightIndex }, + _shapePlumber { shapePlumber }, + _sharedParameters{ parameters } { +} + +void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + auto& inShapes = inputs.get0(); + + if (!_stencilMaskPipeline || !_stencilMaskFillPipeline) { + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(true, false, gpu::LESS_EQUAL); + state->setStencilTest(true, 0xFF, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_ZERO, gpu::State::STENCIL_OP_REPLACE)); + state->setColorWriteMask(false, false, false, false); + state->setCullMode(gpu::State::CULL_FRONT); + + gpu::StatePointer fillState = gpu::StatePointer(new gpu::State()); + fillState->setDepthTest(false, false, gpu::LESS_EQUAL); + fillState->setStencilTest(true, 0xFF, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE)); + fillState->setColorWriteMask(false, false, false, false); + fillState->setCullMode(gpu::State::CULL_FRONT); + + auto vs = gpu::Shader::createVertex(std::string(Highlight_aabox_vert)); + auto ps = gpu::Shader::createPixel(std::string(nop_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + _stencilMaskPipeline = gpu::Pipeline::create(program, state); + _stencilMaskFillPipeline = gpu::Pipeline::create(program, fillState); + } + + if (!_boundsBuffer) { + _boundsBuffer = std::make_shared(sizeof(render::ItemBound)); + } + + auto highlightStage = renderContext->_scene->getStage(render::HighlightStage::getName()); + auto highlightId = _sharedParameters->_highlightIds[_highlightPassIndex]; + + if (!inShapes.empty() && !render::HighlightStage::isIndexInvalid(highlightId)) { + auto ressources = inputs.get1(); + auto& highlight = highlightStage->getHighlight(highlightId); + + RenderArgs* args = renderContext->args; + ShapeKey::Builder defaultKeyBuilder; + + // Render full screen + outputs = args->_viewport; + + // Clear the framebuffer without stereo + // Needs to be distinct from the other batch because using the clear call + // while stereo is enabled triggers a warning + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(ressources->getDepthFramebuffer()); + batch.clearDepthStencilFramebuffer(1.0f, 0); + }); + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + + render::ItemBounds itemBounds; + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + args->_batch = &batch; + + auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); + auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); + + // Setup camera, projection and viewport for all items + batch.setViewportTransform(args->_viewport); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + + std::vector skinnedShapeKeys{}; + + // Iterate through all inShapes and render the unskinned + args->_shapePipeline = maskPipeline; + batch.setPipeline(maskPipeline->pipeline); + for (const auto& items : inShapes) { + itemBounds.insert(itemBounds.end(), items.second.begin(), items.second.end()); + if (items.first.isSkinned()) { + skinnedShapeKeys.push_back(items.first); + } else { + renderItems(renderContext, items.second); + } + } + + // Reiterate to render the skinned + args->_shapePipeline = maskSkinnedPipeline; + batch.setPipeline(maskSkinnedPipeline->pipeline); + for (const auto& key : skinnedShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } + + args->_shapePipeline = nullptr; + args->_batch = nullptr; + }); + + _boundsBuffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data()); + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + // Setup camera, projection and viewport for all items + batch.setViewportTransform(args->_viewport); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + + // Draw stencil mask with object bounding boxes + const auto highlightWidthLoc = _stencilMaskPipeline->getProgram()->getUniforms().findLocation("outlineWidth"); + const auto securityMargin = 2.0f; + const float blurPixelWidth = 2.0f * securityMargin * HighlightSharedParameters::getBlurPixelWidth(highlight._style, args->_viewport.w); + const auto framebufferSize = ressources->getSourceFrameSize(); + + auto stencilPipeline = highlight._style.isFilled() ? _stencilMaskFillPipeline : _stencilMaskPipeline; + batch.setPipeline(stencilPipeline); + batch.setResourceBuffer(0, _boundsBuffer); + batch._glUniform2f(highlightWidthLoc, blurPixelWidth / framebufferSize.x, blurPixelWidth / framebufferSize.y); + static const int NUM_VERTICES_PER_CUBE = 36; + batch.draw(gpu::TRIANGLES, NUM_VERTICES_PER_CUBE * (gpu::uint32) itemBounds.size(), 0); + }); + } else { + // Highlight rect should be null as there are no highlighted shapes + outputs = glm::ivec4(0, 0, 0, 0); + } +} + +gpu::PipelinePointer DrawHighlight::_pipeline; +gpu::PipelinePointer DrawHighlight::_pipelineFilled; + +DrawHighlight::DrawHighlight(unsigned int highlightIndex, HighlightSharedParametersPointer parameters) : + _highlightPassIndex{ highlightIndex }, + _sharedParameters{ parameters } { +} + +void DrawHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { + auto highlightFrameBuffer = inputs.get1(); + auto highlightRect = inputs.get3(); + + if (highlightFrameBuffer && highlightRect.z>0 && highlightRect.w>0) { + auto sceneDepthBuffer = inputs.get2(); + const auto frameTransform = inputs.get0(); + auto highlightedDepthTexture = highlightFrameBuffer->getDepthTexture(); + auto destinationFrameBuffer = highlightFrameBuffer->getColorFramebuffer(); + auto framebufferSize = glm::ivec2(highlightedDepthTexture->getDimensions()); + + if (sceneDepthBuffer) { + auto args = renderContext->args; + + auto highlightStage = renderContext->_scene->getStage(render::HighlightStage::getName()); + auto highlightId = _sharedParameters->_highlightIds[_highlightPassIndex]; + if (!render::HighlightStage::isIndexInvalid(highlightId)) { + auto& highlight = highlightStage->getHighlight(highlightId); + auto pipeline = getPipeline(highlight._style); + { + auto& shaderParameters = _configuration.edit(); + + shaderParameters._color = highlight._style.color; + shaderParameters._intensity = highlight._style.outlineIntensity * (highlight._style.isOutlineSmooth ? 2.0f : 1.0f); + shaderParameters._unoccludedFillOpacity = highlight._style.unoccludedFillOpacity; + shaderParameters._occludedFillOpacity = highlight._style.occludedFillOpacity; + shaderParameters._threshold = highlight._style.isOutlineSmooth ? 1.0f : 1e-3f; + shaderParameters._blurKernelSize = std::min(7, std::max(2, (int)floorf(highlight._style.outlineWidth * 3 + 0.5f))); + // Size is in normalized screen height. We decide that for highlight width = 1, this is equal to 1/400. + auto size = highlight._style.outlineWidth / 400.0f; + shaderParameters._size.x = (size * framebufferSize.y) / framebufferSize.x; + shaderParameters._size.y = size; + } + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(destinationFrameBuffer); + + batch.setViewportTransform(args->_viewport); + batch.setProjectionTransform(glm::mat4()); + batch.resetViewTransform(); + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(framebufferSize, args->_viewport)); + batch.setPipeline(pipeline); + + batch.setUniformBuffer(HIGHLIGHT_PARAMS_SLOT, _configuration); + batch.setUniformBuffer(FRAME_TRANSFORM_SLOT, frameTransform->getFrameTransformBuffer()); + batch.setResourceTexture(SCENE_DEPTH_MAP_SLOT, sceneDepthBuffer->getPrimaryDepthTexture()); + batch.setResourceTexture(HIGHLIGHTED_DEPTH_MAP_SLOT, highlightedDepthTexture); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + } + } + } +} + +const gpu::PipelinePointer& DrawHighlight::getPipeline(const render::HighlightStyle& style) { + if (!_pipeline) { + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(false, false)); + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); + state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); + + auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); + auto ps = gpu::Shader::createPixel(std::string(Highlight_frag)); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("highlightParamsBuffer", HIGHLIGHT_PARAMS_SLOT)); + slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", FRAME_TRANSFORM_SLOT)); + slotBindings.insert(gpu::Shader::Binding("sceneDepthMap", SCENE_DEPTH_MAP_SLOT)); + slotBindings.insert(gpu::Shader::Binding("highlightedDepthMap", HIGHLIGHTED_DEPTH_MAP_SLOT)); + gpu::Shader::makeProgram(*program, slotBindings); + + _pipeline = gpu::Pipeline::create(program, state); + + ps = gpu::Shader::createPixel(std::string(Highlight_filled_frag)); + program = gpu::Shader::createProgram(vs, ps); + gpu::Shader::makeProgram(*program, slotBindings); + _pipelineFilled = gpu::Pipeline::create(program, state); + } + return style.isFilled() ? _pipelineFilled : _pipeline; +} + +DebugHighlight::DebugHighlight() { + _geometryDepthId = DependencyManager::get()->allocateID(); +} + +DebugHighlight::~DebugHighlight() { + auto geometryCache = DependencyManager::get(); + if (geometryCache) { + geometryCache->releaseID(_geometryDepthId); + } +} + +void DebugHighlight::configure(const Config& config) { + _isDisplayEnabled = config.viewMask; +} + +void DebugHighlight::run(const render::RenderContextPointer& renderContext, const Inputs& input) { + const auto highlightRessources = input.get0(); + const auto highlightRect = input.get1(); + + if (_isDisplayEnabled && highlightRessources && highlightRect.z>0 && highlightRect.w>0) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + + gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { + batch.setViewportTransform(args->_viewport); + batch.setFramebuffer(highlightRessources->getColorFramebuffer()); + + const auto geometryBuffer = DependencyManager::get(); + + glm::mat4 projMat; + Transform viewMat; + args->getViewFrustum().evalProjectionMatrix(projMat); + args->getViewFrustum().evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat, true); + batch.setModelTransform(Transform()); + + const glm::vec4 color(1.0f, 1.0f, 1.0f, 1.0f); + + batch.setPipeline(getDepthPipeline()); + batch.setResourceTexture(0, highlightRessources->getDepthTexture()); + const glm::vec2 bottomLeft(-1.0f, -1.0f); + const glm::vec2 topRight(1.0f, 1.0f); + geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryDepthId); + + batch.setResourceTexture(0, nullptr); + }); + } +} + +void DebugHighlight::initializePipelines() { + static const std::string VERTEX_SHADER{ debug_deferred_buffer_vert }; + static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag }; + static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; + static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER); + Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, + "Could not find source placeholder"); + + auto state = std::make_shared(); + state->setDepthTest(gpu::State::DepthTest(false, false)); + state->setStencilTest(true, 0, gpu::State::StencilTest(OUTLINE_STENCIL_MASK, 0xFF, gpu::EQUAL)); + + const auto vs = gpu::Shader::createVertex(VERTEX_SHADER); + + // Depth shader + { + static const std::string DEPTH_SHADER{ + "vec4 getFragmentColor() {" + " float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x;" + " Zdb = 1.0-(1.0-Zdb)*100;" + " return vec4(Zdb, Zdb, Zdb, 1.0); " + "}" + }; + + auto fragmentShader = FRAGMENT_SHADER; + fragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), DEPTH_SHADER); + + const auto ps = gpu::Shader::createPixel(fragmentShader); + const auto program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("depthMap", 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + _depthPipeline = gpu::Pipeline::create(program, state); + } +} + +const gpu::PipelinePointer& DebugHighlight::getDepthPipeline() { + if (!_depthPipeline) { + initializePipelines(); + } + + return _depthPipeline; +} + +void SelectionToHighlight::run(const render::RenderContextPointer& renderContext, Outputs& outputs) { + auto scene = renderContext->_scene; + auto highlightStage = scene->getStage(render::HighlightStage::getName()); + + outputs.clear(); + _sharedParameters->_highlightIds.fill(render::HighlightStage::INVALID_INDEX); + + for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) { + std::ostringstream stream; + if (i > 0) { + stream << "highlightList" << i; + } else { + stream << "contextOverlayHighlightList"; + } + auto selectionName = stream.str(); + if (!scene->isSelectionEmpty(selectionName)) { + auto highlightId = highlightStage->getHighlightIdBySelection(selectionName); + if (!render::HighlightStage::isIndexInvalid(highlightId)) { + _sharedParameters->_highlightIds[outputs.size()] = highlightId; + outputs.emplace_back(selectionName); + } + } + } +} + +void ExtractSelectionName::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { + if (_highlightPassIndex < inputs.size()) { + outputs = inputs[_highlightPassIndex]; + } else { + outputs.clear(); + } +} + +DrawHighlightTask::DrawHighlightTask() { + +} + +void DrawHighlightTask::configure(const Config& config) { + +} + +void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { + const auto items = inputs.getN(0).get(); + const auto sceneFrameBuffer = inputs.getN(1); + const auto primaryFramebuffer = inputs.getN(2); + const auto deferredFrameTransform = inputs.getN(3); + + // Prepare the ShapePipeline + auto shapePlumber = std::make_shared(); + { + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setColorWriteMask(false, false, false, false); + + initMaskPipelines(*shapePlumber, state); + } + auto sharedParameters = std::make_shared(); + + const auto highlightSelectionNames = task.addJob("SelectionToHighlight", sharedParameters); + + // Prepare for highlight group rendering. + const auto highlightRessources = task.addJob("PrepareHighlight", primaryFramebuffer); + render::Varying highlight0Rect; + + for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) { + const auto selectionName = task.addJob("ExtractSelectionName", highlightSelectionNames, i); + const auto groupItems = addSelectItemJobs(task, selectionName, items); + const auto highlightedItemIDs = task.addJob("HighlightMetaToSubItemIDs", groupItems); + const auto highlightedItems = task.addJob("HighlightMetaToSubItems", highlightedItemIDs); + + // Sort + const auto sortedPipelines = task.addJob("HighlightPipelineSort", highlightedItems); + const auto sortedBounds = task.addJob("HighlightDepthSort", sortedPipelines); + + // Draw depth of highlighted objects in separate buffer + std::string name; + { + std::ostringstream stream; + stream << "HighlightMask" << i; + name = stream.str(); + } + const auto drawMaskInputs = DrawHighlightMask::Inputs(sortedBounds, highlightRessources).asVarying(); + const auto highlightedRect = task.addJob(name, drawMaskInputs, i, shapePlumber, sharedParameters); + if (i == 0) { + highlight0Rect = highlightedRect; + } + + // Draw highlight + { + std::ostringstream stream; + stream << "HighlightEffect" << i; + name = stream.str(); + } + const auto drawHighlightInputs = DrawHighlight::Inputs(deferredFrameTransform, highlightRessources, sceneFrameBuffer, highlightedRect).asVarying(); + task.addJob(name, drawHighlightInputs, i, sharedParameters); + } + + // Debug highlight + const auto debugInputs = DebugHighlight::Inputs(highlightRessources, const_cast(highlight0Rect)).asVarying(); + task.addJob("HighlightDebug", debugInputs); +} + +const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const render::Varying& selectionName, + const RenderFetchCullSortTask::BucketList& items) { + const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; + const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; + const auto& metas = items[RenderFetchCullSortTask::META]; + + const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), selectionName).asVarying(); + const auto selectedMetas = task.addJob("MetaSelection", selectMetaInput); + const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas, selectionName).asVarying(); + const auto selectedMetasAndOpaques = task.addJob("OpaqueSelection", selectMetaAndOpaqueInput); + const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, selectionName).asVarying(); + return task.addJob("TransparentSelection", selectItemInput); +} + +#include "model_shadow_vert.h" +#include "skin_model_shadow_vert.h" + +#include "model_shadow_frag.h" + +void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) { + auto modelVertex = gpu::Shader::createVertex(std::string(model_shadow_vert)); + auto modelPixel = gpu::Shader::createPixel(std::string(model_shadow_frag)); + gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(modelVertex, modelPixel); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withoutSkinned(), + modelProgram, state); + + auto skinVertex = gpu::Shader::createVertex(std::string(skin_model_shadow_vert)); + gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(skinVertex, modelPixel); + shapePlumber.addPipeline( + ShapeKey::Filter::Builder().withSkinned(), + skinProgram, state); +} diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h new file mode 100644 index 0000000000..90a8e730ce --- /dev/null +++ b/libraries/render-utils/src/HighlightEffect.h @@ -0,0 +1,226 @@ +// +// HighlightEffect.h +// render-utils/src/ +// +// Created by Olivier Prat on 08/08/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 +// + +#ifndef hifi_render_utils_HighlightEffect_h +#define hifi_render_utils_HighlightEffect_h + +#include +#include +#include + +#include "DeferredFramebuffer.h" +#include "DeferredFrameTransform.h" + +class HighlightRessources { +public: + HighlightRessources(); + + gpu::FramebufferPointer getDepthFramebuffer(); + gpu::TexturePointer getDepthTexture(); + + gpu::FramebufferPointer getColorFramebuffer(); + + // Update the source framebuffer size which will drive the allocation of all the other resources. + void update(const gpu::FramebufferPointer& primaryFrameBuffer); + const glm::ivec2& getSourceFrameSize() const { return _frameSize; } + +protected: + + gpu::FramebufferPointer _depthFrameBuffer; + gpu::FramebufferPointer _colorFrameBuffer; + gpu::TexturePointer _depthStencilTexture; + + glm::ivec2 _frameSize; + + void allocateColorBuffer(const gpu::FramebufferPointer& primaryFrameBuffer); + void allocateDepthBuffer(const gpu::FramebufferPointer& primaryFrameBuffer); +}; + +using HighlightRessourcesPointer = std::shared_ptr; + +class HighlightSharedParameters { +public: + + enum { + MAX_PASS_COUNT = 8 + }; + + HighlightSharedParameters(); + + std::array _highlightIds; + + static float getBlurPixelWidth(const render::HighlightStyle& style, int frameBufferHeight); +}; + +using HighlightSharedParametersPointer = std::shared_ptr; + +class PrepareDrawHighlight { +public: + using Inputs = gpu::FramebufferPointer; + using Outputs = HighlightRessourcesPointer; + using JobModel = render::Job::ModelIO; + + PrepareDrawHighlight(); + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +private: + + HighlightRessourcesPointer _ressources; + +}; + +class SelectionToHighlight { +public: + + using Outputs = std::vector; + using JobModel = render::Job::ModelO; + + SelectionToHighlight(HighlightSharedParametersPointer parameters) : _sharedParameters{ parameters } {} + + void run(const render::RenderContextPointer& renderContext, Outputs& outputs); + +private: + + HighlightSharedParametersPointer _sharedParameters; +}; + +class ExtractSelectionName { +public: + + using Inputs = SelectionToHighlight::Outputs; + using Outputs = std::string; + using JobModel = render::Job::ModelIO; + + ExtractSelectionName(unsigned int highlightIndex) : _highlightPassIndex{ highlightIndex } {} + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +private: + + unsigned int _highlightPassIndex; + +}; + +class DrawHighlightMask { +public: + + using Inputs = render::VaryingSet2; + using Outputs = glm::ivec4; + using JobModel = render::Job::ModelIO; + + DrawHighlightMask(unsigned int highlightIndex, render::ShapePlumberPointer shapePlumber, HighlightSharedParametersPointer parameters); + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + +protected: + + unsigned int _highlightPassIndex; + render::ShapePlumberPointer _shapePlumber; + HighlightSharedParametersPointer _sharedParameters; + gpu::BufferPointer _boundsBuffer; + + static gpu::PipelinePointer _stencilMaskPipeline; + static gpu::PipelinePointer _stencilMaskFillPipeline; +}; + +class DrawHighlight { +public: + + using Inputs = render::VaryingSet4; + using Config = render::Job::Config; + using JobModel = render::Job::ModelI; + + DrawHighlight(unsigned int highlightIndex, HighlightSharedParametersPointer parameters); + + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + +private: + +#include "Highlight_shared.slh" + + enum { + SCENE_DEPTH_MAP_SLOT = 0, + HIGHLIGHTED_DEPTH_MAP_SLOT, + + HIGHLIGHT_PARAMS_SLOT = 0, + FRAME_TRANSFORM_SLOT, + }; + + using HighlightConfigurationBuffer = gpu::StructBuffer; + + static const gpu::PipelinePointer& getPipeline(const render::HighlightStyle& style); + + static gpu::PipelinePointer _pipeline; + static gpu::PipelinePointer _pipelineFilled; + + unsigned int _highlightPassIndex; + HighlightParameters _parameters; + HighlightSharedParametersPointer _sharedParameters; + HighlightConfigurationBuffer _configuration; +}; + +class DebugHighlightConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(bool viewMask MEMBER viewMask NOTIFY dirty) + +public: + + bool viewMask{ false }; + +signals: + void dirty(); +}; + +class DebugHighlight { +public: + using Inputs = render::VaryingSet2; + using Config = DebugHighlightConfig; + using JobModel = render::Job::ModelI; + + DebugHighlight(); + ~DebugHighlight(); + + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); + +private: + + gpu::PipelinePointer _depthPipeline; + int _geometryDepthId{ 0 }; + bool _isDisplayEnabled{ false }; + + const gpu::PipelinePointer& getDepthPipeline(); + void initializePipelines(); +}; + +class DrawHighlightTask { +public: + + using Inputs = render::VaryingSet4; + using Config = render::Task::Config; + using JobModel = render::Task::ModelI; + + DrawHighlightTask(); + + void configure(const Config& config); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); + +private: + + static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state); + static const render::Varying addSelectItemJobs(JobModel& task, const render::Varying& selectionName, const RenderFetchCullSortTask::BucketList& items); + +}; + +#endif // hifi_render_utils_HighlightEffect_h + + diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv new file mode 100644 index 0000000000..4927db9610 --- /dev/null +++ b/libraries/render-utils/src/Highlight_aabox.slv @@ -0,0 +1,104 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Draw and transform the fed vertex position with the standard MVP stack +// and offset the vertices by a certain amount in the vertex direction +// +// Created by Olivier Prat on 11/02/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 +// + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +struct ItemBound { + vec4 id_boundPos; + vec4 boundDim_s; +}; + +#if defined(GPU_GL410) +uniform samplerBuffer ssbo0Buffer; +ItemBound getItemBound(int i) { + int offset = 2 * i; + ItemBound bound; + bound.id_boundPos = texelFetch(ssbo0Buffer, offset); + bound.boundDim_s = texelFetch(ssbo0Buffer, offset + 1); + return bound; +} +#else +layout(std140) buffer ssbo0Buffer { + ItemBound bounds[]; +}; +ItemBound getItemBound(int i) { + ItemBound bound = bounds[i]; + return bound; +} +#endif + +uniform vec2 outlineWidth; + +void main(void) { + const vec3 UNIT_BOX_VERTICES[8] = vec3[8]( + vec3(0.0, 1.0, 0.0), + vec3(1.0, 1.0, 0.0), + vec3(1.0, 0.0, 0.0), + vec3(0.0, 0.0, 0.0), + vec3(0.0, 1.0, 1.0), + vec3(1.0, 1.0, 1.0), + vec3(1.0, 0.0, 1.0), + vec3(0.0, 0.0, 1.0) + ); + const vec3 UNIT_BOX_NORMALS[8] = vec3[8]( + vec3(-1.0, 1.0, -1.0), + vec3(1.0, 1.0, -1.0), + vec3(1.0, -1.0, -1.0), + vec3(-1.0, -1.0, -1.0), + vec3(-1.0, 1.0, 1.0), + vec3(1.0, 1.0, 1.0), + vec3(1.0, -1.0, 1.0), + vec3(-1.0, -1.0, 1.0) + ); + const int NUM_VERTICES_PER_CUBE = 36; + const int UNIT_BOX_TRIANGLE_INDICES[NUM_VERTICES_PER_CUBE] = int[NUM_VERTICES_PER_CUBE]( + 0, 1, 2, + 0, 2, 3, + 3, 2, 6, + 3, 6, 7, + 7, 6, 5, + 7, 5, 4, + 4, 5, 1, + 4, 1, 0, + 1, 5, 6, + 1, 6, 2, + 4, 0, 3, + 4, 3, 7 + ); + + int boundID = gl_VertexID / NUM_VERTICES_PER_CUBE; + int vertexID = gl_VertexID - boundID * NUM_VERTICES_PER_CUBE; + int triangleIndex = UNIT_BOX_TRIANGLE_INDICES[vertexID]; + vec3 cubeVec = UNIT_BOX_VERTICES[triangleIndex]; + + ItemBound bound = getItemBound(boundID); + vec3 boundPos = bound.id_boundPos.yzw; + vec3 boundDim = bound.boundDim_s.xyz; + + vec4 pos = vec4(boundPos + boundDim * cubeVec.xyz, 1.0); + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToMonoClipPos(cam, obj, pos, gl_Position)$> + + // Offset the vertex to take into account the outline width + pos.xyz += UNIT_BOX_NORMALS[triangleIndex]; + vec4 offsetPosition; + <$transformModelToMonoClipPos(cam, obj, pos, offsetPosition)$> + gl_Position.xy += normalize(offsetPosition.xy-gl_Position.xy) * outlineWidth * gl_Position.w; + <$transformStereoClipsSpace(cam, gl_Position)$> +} diff --git a/libraries/render-utils/src/Outline_filled.slf b/libraries/render-utils/src/Highlight_filled.slf similarity index 71% rename from libraries/render-utils/src/Outline_filled.slf rename to libraries/render-utils/src/Highlight_filled.slf index aaa3396bac..53530746f0 100644 --- a/libraries/render-utils/src/Outline_filled.slf +++ b/libraries/render-utils/src/Highlight_filled.slf @@ -1,5 +1,5 @@ -// Outline_filled.slf -// Add outline effect based on two zbuffers : one containing the total scene z and another +// Highlight_filled.slf +// Add highlight effect based on two zbuffers : one containing the total scene z and another // with the z of only the objects to be outlined. // This is the version with the fill effect inside the silhouette. // @@ -9,5 +9,5 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include Outline.slh@> +<@include Highlight.slh@> <$main(1)$> diff --git a/libraries/render-utils/src/Highlight_shared.slh b/libraries/render-utils/src/Highlight_shared.slh new file mode 100644 index 0000000000..5efbde4d52 --- /dev/null +++ b/libraries/render-utils/src/Highlight_shared.slh @@ -0,0 +1,30 @@ +// glsl / C++ compatible source as interface for highlight +#ifdef __cplusplus +# define TVEC2 glm::vec2 +# define TVEC3 glm::vec3 +# define TVEC4 glm::vec4 +#else +# define TVEC2 vec2 +# define TVEC3 vec3 +# define TVEC4 vec4 +#endif + +struct HighlightParameters +{ + TVEC3 _color; + float _intensity; + + TVEC2 _size; + float _unoccludedFillOpacity; + float _occludedFillOpacity; + + float _threshold; + int _blurKernelSize; + float padding2; + float padding3; +}; + +// <@if 1@> +// Trigger Scribe include +// <@endif@> +// diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index 079c63f367..c280abfeaf 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -14,6 +14,7 @@ #include "LightStage.h" std::string LightStage::_stageName { "LIGHT_STAGE"}; +const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; LightStage::LightStage() { } diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 66d73c9a6e..c26f504658 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -32,7 +32,7 @@ public: static const std::string& getName() { return _stageName; } using Index = render::indexed_container::Index; - static const Index INVALID_INDEX { render::indexed_container::INVALID_INDEX }; + static const Index INVALID_INDEX; static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } using LightPointer = model::LightPointer; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 8b65c9f7ce..1ea3e1a705 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -337,7 +337,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in if (state.clusterMatrices.size() == 1) { renderTransform = transform.worldTransform(Transform(state.clusterMatrices[0])); } - updateTransformForSkinnedMesh(renderTransform, transform, state.clusterBuffer); + updateTransformForSkinnedMesh(renderTransform, transform); initCache(); } @@ -367,12 +367,25 @@ void ModelMeshPartPayload::notifyLocationChanged() { } -void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform, - const gpu::BufferPointer& buffer) { + +void ModelMeshPartPayload::updateClusterBuffer(const std::vector& clusterMatrices) { + // Once computed the cluster matrices, update the buffer(s) + if (clusterMatrices.size() > 1) { + if (!_clusterBuffer) { + _clusterBuffer = std::make_shared(clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) clusterMatrices.data()); + } + else { + _clusterBuffer->setSubData(0, clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) clusterMatrices.data()); + } + } +} + +void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform) { _transform = renderTransform; _worldBound = _adjustedLocalBound; _worldBound.transform(boundTransform); - _clusterBuffer = buffer; } ItemKey ModelMeshPartPayload::getKey() const { @@ -416,7 +429,6 @@ int ModelMeshPartPayload::getLayer() const { } ShapeKey ModelMeshPartPayload::getShapeKey() const { - // guard against partially loaded meshes ModelPointer model = _model.lock(); if (!model || !model->isLoaded() || !model->getGeometry()) { @@ -582,11 +594,11 @@ void ModelMeshPartPayload::render(RenderArgs* args) { args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE; } -void ModelMeshPartPayload::computeAdjustedLocalBound(const QVector& clusterMatrices) { +void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector& clusterMatrices) { _adjustedLocalBound = _localBound; if (clusterMatrices.size() > 0) { _adjustedLocalBound.transform(clusterMatrices[0]); - for (int i = 1; i < clusterMatrices.size(); ++i) { + for (int i = 1; i < (int)clusterMatrices.size(); ++i) { AABox clusterBound = _localBound; clusterBound.transform(clusterMatrices[i]); _adjustedLocalBound += clusterBound; diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 99c14510b5..971c6fe90b 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -87,9 +87,8 @@ public: typedef Payload::DataPointer Pointer; void notifyLocationChanged() override; - void updateTransformForSkinnedMesh(const Transform& renderTransform, - const Transform& boundTransform, - const gpu::BufferPointer& buffer); + void updateClusterBuffer(const std::vector& clusterMatrices); + void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); // Render Item interface render::ItemKey getKey() const override; @@ -103,7 +102,7 @@ public: void initCache(); - void computeAdjustedLocalBound(const QVector& clusterMatrices); + void computeAdjustedLocalBound(const std::vector& clusterMatrices); gpu::BufferPointer _clusterBuffer; ModelWeakPointer _model; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 97f62a3ce0..428fcc7a54 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -226,7 +226,7 @@ void Model::updateRenderItems() { // do nothing, if the model has already been destroyed. auto self = weakSelf.lock(); - if (!self) { + if (!self || !self->isLoaded()) { return; } @@ -237,24 +237,20 @@ void Model::updateRenderItems() { Transform modelTransform = self->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); - uint32_t deleteGeometryCounter = self->_deleteGeometryCounter; - render::Transaction transaction; - foreach (auto itemID, self->_modelMeshRenderItemsMap.keys()) { - transaction.updateItem(itemID, [deleteGeometryCounter, modelTransform](ModelMeshPartPayload& data) { - ModelPointer model = data._model.lock(); - if (model && model->isLoaded()) { - // Ensure the model geometry was not reset between frames - if (deleteGeometryCounter == model->_deleteGeometryCounter) { + for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { - const Model::MeshState& state = model->getMeshState(data._meshIndex); - Transform renderTransform = modelTransform; - if (state.clusterMatrices.size() == 1) { - renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0])); - } - data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer); - } + auto itemID = self->_modelMeshRenderItemIDs[i]; + auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex; + auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices); + + transaction.updateItem(itemID, [modelTransform, clusterMatrices](ModelMeshPartPayload& data) { + data.updateClusterBuffer(clusterMatrices); + Transform renderTransform = modelTransform; + if (clusterMatrices.size() == 1) { + renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0])); } + data.updateTransformForSkinnedMesh(renderTransform, modelTransform); }); } @@ -311,7 +307,7 @@ bool Model::updateGeometry() { foreach (const FBXMesh& mesh, fbxGeometry.meshes) { MeshState state; state.clusterMatrices.resize(mesh.clusters.size()); - _meshStates.append(state); + _meshStates.push_back(state); // Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index // later in ModelMeshPayload, however the vast majority of meshes will not have them. @@ -686,6 +682,7 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti _modelMeshRenderItemIDs.clear(); _modelMeshRenderItemsMap.clear(); _modelMeshRenderItems.clear(); + _modelMeshRenderItemShapes.clear(); foreach(auto item, _collisionRenderItemsMap.keys()) { transaction.removeItem(item); @@ -1127,7 +1124,7 @@ void Model::updateClusterMatrices() { } _needsUpdateClusterMatrices = false; const FBXGeometry& geometry = getFBXGeometry(); - for (int i = 0; i < _meshStates.size(); i++) { + for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { @@ -1135,17 +1132,6 @@ void Model::updateClusterMatrices() { auto jointMatrix = _rig.getJointTransform(cluster.jointIndex); glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } - - // Once computed the cluster matrices, update the buffer(s) - if (mesh.clusters.size() > 1) { - if (!state.clusterBuffer) { - state.clusterBuffer = std::make_shared(state.clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) state.clusterMatrices.constData()); - } else { - state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) state.clusterMatrices.constData()); - } - } } // post the blender if we're not currently waiting for one to finish @@ -1255,7 +1241,7 @@ void Model::createVisibleRenderItemSet() { const auto& meshes = _renderGeometry->getMeshes(); // all of our mesh vectors must match in size - if ((int)meshes.size() != _meshStates.size()) { + if (meshes.size() != _meshStates.size()) { qCDebug(renderutils) << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; return; } @@ -1264,6 +1250,7 @@ void Model::createVisibleRenderItemSet() { Q_ASSERT(_modelMeshRenderItems.isEmpty()); _modelMeshRenderItems.clear(); + _modelMeshRenderItemShapes.clear(); Transform transform; transform.setTranslation(_translation); @@ -1286,6 +1273,7 @@ void Model::createVisibleRenderItemSet() { int numParts = (int)mesh->getNumParts(); for (int partIndex = 0; partIndex < numParts; partIndex++) { _modelMeshRenderItems << std::make_shared(shared_from_this(), i, partIndex, shapeID, transform, offset); + _modelMeshRenderItemShapes.emplace_back(ShapeInfo{ (int)i }); shapeID++; } } @@ -1327,7 +1315,7 @@ void Model::createCollisionRenderItemSet() { } bool Model::isRenderable() const { - return !_meshStates.isEmpty() || (isLoaded() && _renderGeometry->getMeshes().empty()); + return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty()); } class CollisionRenderGeometry : public Geometry { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 3abf7e2758..c537a928b3 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -246,8 +246,7 @@ public: class MeshState { public: - QVector clusterMatrices; - gpu::BufferPointer clusterBuffer; + std::vector clusterMatrices; }; const MeshState& getMeshState(int index) { return _meshStates.at(index); } @@ -317,7 +316,7 @@ protected: bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to - QVector _meshStates; + std::vector _meshStates; virtual void initJointStates(); @@ -388,8 +387,9 @@ protected: QVector> _modelMeshRenderItems; QMap _modelMeshRenderItemsMap; - render::ItemIDs _modelMeshRenderItemIDs; + using ShapeInfo = struct { int meshIndex; }; + std::vector _modelMeshRenderItemShapes; bool _addedToScene { false }; // has been added to scene bool _needsFixupInScene { true }; // needs to be removed/re-added to scene diff --git a/libraries/render-utils/src/OutlineEffect.cpp b/libraries/render-utils/src/OutlineEffect.cpp deleted file mode 100644 index d5b3b1c3bb..0000000000 --- a/libraries/render-utils/src/OutlineEffect.cpp +++ /dev/null @@ -1,371 +0,0 @@ -// -// OutlineEffect.cpp -// render-utils/src/ -// -// Created by Olivier Prat on 08/08/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 "OutlineEffect.h" - -#include "GeometryCache.h" - -#include -#include - -#include "gpu/Context.h" -#include "gpu/StandardShaderLib.h" - - -#include "surfaceGeometry_copyDepth_frag.h" -#include "debug_deferred_buffer_vert.h" -#include "debug_deferred_buffer_frag.h" -#include "Outline_frag.h" -#include "Outline_filled_frag.h" - -using namespace render; - -extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); - -OutlineFramebuffer::OutlineFramebuffer() { -} - -void OutlineFramebuffer::update(const gpu::TexturePointer& colorBuffer) { - // If the depth buffer or size changed, we need to delete our FBOs and recreate them at the - // new correct dimensions. - if (_depthTexture) { - auto newFrameSize = glm::ivec2(colorBuffer->getDimensions()); - if (_frameSize != newFrameSize) { - _frameSize = newFrameSize; - clear(); - } - } -} - -void OutlineFramebuffer::clear() { - _depthFramebuffer.reset(); - _depthTexture.reset(); -} - -void OutlineFramebuffer::allocate() { - - auto width = _frameSize.x; - auto height = _frameSize.y; - auto format = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); - - _depthTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(format, width, height)); - _depthFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("outlineDepth")); - _depthFramebuffer->setDepthStencilBuffer(_depthTexture, format); -} - -gpu::FramebufferPointer OutlineFramebuffer::getDepthFramebuffer() { - if (!_depthFramebuffer) { - allocate(); - } - return _depthFramebuffer; -} - -gpu::TexturePointer OutlineFramebuffer::getDepthTexture() { - if (!_depthTexture) { - allocate(); - } - return _depthTexture; -} - -void DrawOutlineDepth::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - auto& inShapes = inputs.get0(); - auto& deferredFrameBuffer = inputs.get1(); - - if (!inShapes.empty()) { - RenderArgs* args = renderContext->args; - ShapeKey::Builder defaultKeyBuilder; - - if (!_outlineFramebuffer) { - _outlineFramebuffer = std::make_shared(); - } - _outlineFramebuffer->update(deferredFrameBuffer->getDeferredColorTexture()); - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - args->_batch = &batch; - - batch.setFramebuffer(_outlineFramebuffer->getDepthFramebuffer()); - // Clear it - batch.clearFramebuffer( - gpu::Framebuffer::BUFFER_DEPTH, - vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, false); - - // Setup camera, projection and viewport for all items - batch.setViewportTransform(args->_viewport); - batch.setStateScissorRect(args->_viewport); - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); - - auto shadowPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); - auto shadowSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); - - std::vector skinnedShapeKeys{}; - - // Iterate through all inShapes and render the unskinned - args->_shapePipeline = shadowPipeline; - batch.setPipeline(shadowPipeline->pipeline); - for (auto items : inShapes) { - if (items.first.isSkinned()) { - skinnedShapeKeys.push_back(items.first); - } - else { - renderItems(renderContext, items.second); - } - } - - // Reiterate to render the skinned - args->_shapePipeline = shadowSkinnedPipeline; - batch.setPipeline(shadowSkinnedPipeline->pipeline); - for (const auto& key : skinnedShapeKeys) { - renderItems(renderContext, inShapes.at(key)); - } - - args->_shapePipeline = nullptr; - args->_batch = nullptr; - }); - - output = _outlineFramebuffer; - } else { - output = nullptr; - } -} - -DrawOutline::DrawOutline() { -} - -void DrawOutline::configure(const Config& config) { - _color = config.color; - _blurKernelSize = std::min(10, std::max(2, (int)floorf(config.width*2 + 0.5f))); - // Size is in normalized screen height. We decide that for outline width = 1, this is equal to 1/400. - _size = config.width / 400.f; - _fillOpacityUnoccluded = config.fillOpacityUnoccluded; - _fillOpacityOccluded = config.fillOpacityOccluded; - _threshold = config.glow ? 1.f : 1e-3f; - _intensity = config.intensity * (config.glow ? 2.f : 1.f); -} - -void DrawOutline::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { - auto outlineFrameBuffer = inputs.get1(); - - if (outlineFrameBuffer) { - auto sceneDepthBuffer = inputs.get2(); - const auto frameTransform = inputs.get0(); - auto outlinedDepthTexture = outlineFrameBuffer->getDepthTexture(); - auto destinationFrameBuffer = inputs.get3(); - auto framebufferSize = glm::ivec2(outlinedDepthTexture->getDimensions()); - - if (!_primaryWithoutDepthBuffer || framebufferSize!=_frameBufferSize) { - // Failing to recreate this frame buffer when the screen has been resized creates a bug on Mac - _primaryWithoutDepthBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("primaryWithoutDepth")); - _primaryWithoutDepthBuffer->setRenderBuffer(0, destinationFrameBuffer->getRenderBuffer(0)); - _frameBufferSize = framebufferSize; - } - - if (sceneDepthBuffer) { - const auto OPACITY_EPSILON = 5e-3f; - auto pipeline = getPipeline(_fillOpacityUnoccluded>OPACITY_EPSILON || _fillOpacityOccluded>OPACITY_EPSILON); - auto args = renderContext->args; - { - auto& configuration = _configuration.edit(); - configuration._color = _color; - configuration._intensity = _intensity; - configuration._fillOpacityUnoccluded = _fillOpacityUnoccluded; - configuration._fillOpacityOccluded = _fillOpacityOccluded; - configuration._threshold = _threshold; - configuration._blurKernelSize = _blurKernelSize; - configuration._size.x = _size * _frameBufferSize.y / _frameBufferSize.x; - configuration._size.y = _size; - } - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(_primaryWithoutDepthBuffer); - - batch.setViewportTransform(args->_viewport); - batch.setProjectionTransform(glm::mat4()); - batch.resetViewTransform(); - batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(_frameBufferSize, args->_viewport)); - batch.setPipeline(pipeline); - - batch.setUniformBuffer(OUTLINE_PARAMS_SLOT, _configuration); - batch.setUniformBuffer(FRAME_TRANSFORM_SLOT, frameTransform->getFrameTransformBuffer()); - batch.setResourceTexture(SCENE_DEPTH_SLOT, sceneDepthBuffer->getPrimaryDepthTexture()); - batch.setResourceTexture(OUTLINED_DEPTH_SLOT, outlinedDepthTexture); - batch.draw(gpu::TRIANGLE_STRIP, 4); - - // Restore previous frame buffer - batch.setFramebuffer(destinationFrameBuffer); - }); - } - } -} - -const gpu::PipelinePointer& DrawOutline::getPipeline(bool isFilled) { - if (!_pipeline) { - auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); - auto ps = gpu::Shader::createPixel(std::string(Outline_frag)); - gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("outlineParamsBuffer", OUTLINE_PARAMS_SLOT)); - slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", FRAME_TRANSFORM_SLOT)); - slotBindings.insert(gpu::Shader::Binding("sceneDepthMap", SCENE_DEPTH_SLOT)); - slotBindings.insert(gpu::Shader::Binding("outlinedDepthMap", OUTLINED_DEPTH_SLOT)); - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false, false)); - state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - _pipeline = gpu::Pipeline::create(program, state); - - ps = gpu::Shader::createPixel(std::string(Outline_filled_frag)); - program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::makeProgram(*program, slotBindings); - _pipelineFilled = gpu::Pipeline::create(program, state); - } - return isFilled ? _pipelineFilled : _pipeline; -} - -DebugOutline::DebugOutline() { - _geometryId = DependencyManager::get()->allocateID(); -} - -DebugOutline::~DebugOutline() { - auto geometryCache = DependencyManager::get(); - if (geometryCache) { - geometryCache->releaseID(_geometryId); - } -} - -void DebugOutline::configure(const Config& config) { - _isDisplayDepthEnabled = config.viewOutlinedDepth; -} - -void DebugOutline::run(const render::RenderContextPointer& renderContext, const Inputs& input) { - const auto outlineFramebuffer = input; - - if (_isDisplayDepthEnabled && outlineFramebuffer) { - assert(renderContext->args); - assert(renderContext->args->hasViewFrustum()); - RenderArgs* args = renderContext->args; - - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setViewportTransform(args->_viewport); - - const auto geometryBuffer = DependencyManager::get(); - - glm::mat4 projMat; - Transform viewMat; - args->getViewFrustum().evalProjectionMatrix(projMat); - args->getViewFrustum().evalViewTransform(viewMat); - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat, true); - batch.setModelTransform(Transform()); - - batch.setPipeline(getDebugPipeline()); - batch.setResourceTexture(0, outlineFramebuffer->getDepthTexture()); - - const glm::vec4 color(1.0f, 0.5f, 0.2f, 1.0f); - const glm::vec2 bottomLeft(-1.0f, -1.0f); - const glm::vec2 topRight(1.0f, 1.0f); - geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryId); - - batch.setResourceTexture(0, nullptr); - }); - } -} - -const gpu::PipelinePointer& DebugOutline::getDebugPipeline() { - if (!_debugPipeline) { - static const std::string VERTEX_SHADER{ debug_deferred_buffer_vert }; - static const std::string FRAGMENT_SHADER{ debug_deferred_buffer_frag }; - static const std::string SOURCE_PLACEHOLDER{ "//SOURCE_PLACEHOLDER" }; - static const auto SOURCE_PLACEHOLDER_INDEX = FRAGMENT_SHADER.find(SOURCE_PLACEHOLDER); - Q_ASSERT_X(SOURCE_PLACEHOLDER_INDEX != std::string::npos, Q_FUNC_INFO, - "Could not find source placeholder"); - static const std::string DEFAULT_DEPTH_SHADER{ - "vec4 getFragmentColor() {" - " float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x;" - " Zdb = 1.0-(1.0-Zdb)*100;" - " return vec4(Zdb, Zdb, Zdb, 1.0);" - " }" - }; - - auto bakedFragmentShader = FRAGMENT_SHADER; - bakedFragmentShader.replace(SOURCE_PLACEHOLDER_INDEX, SOURCE_PLACEHOLDER.size(), DEFAULT_DEPTH_SHADER); - - static const auto vs = gpu::Shader::createVertex(VERTEX_SHADER); - const auto ps = gpu::Shader::createPixel(bakedFragmentShader); - const auto program = gpu::Shader::createProgram(vs, ps); - - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("depthMap", 0)); - gpu::Shader::makeProgram(*program, slotBindings); - - auto state = std::make_shared(); - state->setDepthTest(gpu::State::DepthTest(false)); - _debugPipeline = gpu::Pipeline::create(program, state); - } - - return _debugPipeline; -} - -DrawOutlineTask::DrawOutlineTask() { - -} - -void DrawOutlineTask::configure(const Config& config) { - -} - -void DrawOutlineTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { - const auto input = inputs.get(); - const auto selectedMetas = inputs.getN(0); - const auto shapePlumber = input.get1(); - const auto sceneFrameBuffer = inputs.getN(2); - const auto primaryFramebuffer = inputs.getN(3); - const auto deferredFrameTransform = inputs.getN(4); - - // Prepare the ShapePipeline - ShapePlumberPointer shapePlumberZPass = std::make_shared(); - { - auto state = std::make_shared(); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - state->setColorWriteMask(false, false, false, false); - - initZPassPipelines(*shapePlumberZPass, state); - } - - const auto outlinedItemIDs = task.addJob("OutlineMetaToSubItemIDs", selectedMetas); - const auto outlinedItems = task.addJob("OutlineMetaToSubItems", outlinedItemIDs, true); - - // Sort - const auto sortedPipelines = task.addJob("OutlinePipelineSort", outlinedItems); - const auto sortedShapes = task.addJob("OutlineDepthSort", sortedPipelines); - - // Draw depth of outlined objects in separate buffer - const auto drawOutlineDepthInputs = DrawOutlineDepth::Inputs(sortedShapes, sceneFrameBuffer).asVarying(); - const auto outlinedFrameBuffer = task.addJob("OutlineDepth", drawOutlineDepthInputs, shapePlumberZPass); - - // Draw outline - const auto drawOutlineInputs = DrawOutline::Inputs(deferredFrameTransform, outlinedFrameBuffer, sceneFrameBuffer, primaryFramebuffer).asVarying(); - task.addJob("OutlineEffect", drawOutlineInputs); - - // Debug outline - task.addJob("OutlineDebug", outlinedFrameBuffer); -} diff --git a/libraries/render-utils/src/OutlineEffect.h b/libraries/render-utils/src/OutlineEffect.h deleted file mode 100644 index f88092429f..0000000000 --- a/libraries/render-utils/src/OutlineEffect.h +++ /dev/null @@ -1,183 +0,0 @@ -// -// OutlineEffect.h -// render-utils/src/ -// -// Created by Olivier Prat on 08/08/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 -// - -#ifndef hifi_render_utils_OutlineEffect_h -#define hifi_render_utils_OutlineEffect_h - -#include -#include "DeferredFramebuffer.h" -#include "DeferredFrameTransform.h" - -class OutlineFramebuffer { -public: - OutlineFramebuffer(); - - gpu::FramebufferPointer getDepthFramebuffer(); - gpu::TexturePointer getDepthTexture(); - - // Update the source framebuffer size which will drive the allocation of all the other resources. - void update(const gpu::TexturePointer& colorBuffer); - const glm::ivec2& getSourceFrameSize() const { return _frameSize; } - -protected: - - void clear(); - void allocate(); - - gpu::FramebufferPointer _depthFramebuffer; - gpu::TexturePointer _depthTexture; - - glm::ivec2 _frameSize; -}; - -using OutlineFramebufferPointer = std::shared_ptr; - -class DrawOutlineDepth { -public: - - using Inputs = render::VaryingSet2; - // Output will contain outlined objects only z-depth texture and the input primary buffer but without the primary depth buffer - using Outputs = OutlineFramebufferPointer; - using JobModel = render::Job::ModelIO; - - DrawOutlineDepth(render::ShapePlumberPointer shapePlumber) : _shapePlumber{ shapePlumber } {} - - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output); - -protected: - - render::ShapePlumberPointer _shapePlumber; - OutlineFramebufferPointer _outlineFramebuffer; -}; - -class DrawOutlineConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(bool glow MEMBER glow NOTIFY dirty) - Q_PROPERTY(float width MEMBER width NOTIFY dirty) - Q_PROPERTY(float intensity MEMBER intensity NOTIFY dirty) - Q_PROPERTY(float colorR READ getColorR WRITE setColorR NOTIFY dirty) - Q_PROPERTY(float colorG READ getColorG WRITE setColorG NOTIFY dirty) - Q_PROPERTY(float colorB READ getColorB WRITE setColorB NOTIFY dirty) - Q_PROPERTY(float fillOpacityUnoccluded MEMBER fillOpacityUnoccluded NOTIFY dirty) - Q_PROPERTY(float fillOpacityOccluded MEMBER fillOpacityOccluded NOTIFY dirty) - -public: - - void setColorR(float value) { color.r = value; emit dirty(); } - float getColorR() const { return color.r; } - - void setColorG(float value) { color.g = value; emit dirty(); } - float getColorG() const { return color.g; } - - void setColorB(float value) { color.b = value; emit dirty(); } - float getColorB() const { return color.b; } - - glm::vec3 color{ 1.f, 0.7f, 0.2f }; - float width{ 2.0f }; - float intensity{ 0.9f }; - float fillOpacityUnoccluded{ 0.0f }; - float fillOpacityOccluded{ 0.0f }; - bool glow{ false }; - -signals: - void dirty(); -}; - -class DrawOutline { -public: - using Inputs = render::VaryingSet4; - using Config = DrawOutlineConfig; - using JobModel = render::Job::ModelI; - - DrawOutline(); - - void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); - -private: - - enum { - SCENE_DEPTH_SLOT = 0, - OUTLINED_DEPTH_SLOT, - - OUTLINE_PARAMS_SLOT = 0, - FRAME_TRANSFORM_SLOT - }; - -#include "Outline_shared.slh" - - using OutlineConfigurationBuffer = gpu::StructBuffer; - - const gpu::PipelinePointer& getPipeline(bool isFilled); - - gpu::FramebufferPointer _primaryWithoutDepthBuffer; - glm::ivec2 _frameBufferSize {0, 0}; - gpu::PipelinePointer _pipeline; - gpu::PipelinePointer _pipelineFilled; - OutlineConfigurationBuffer _configuration; - glm::vec3 _color; - float _size; - int _blurKernelSize; - float _intensity; - float _fillOpacityUnoccluded; - float _fillOpacityOccluded; - float _threshold; -}; - -class DebugOutlineConfig : public render::Job::Config { - Q_OBJECT - Q_PROPERTY(bool viewOutlinedDepth MEMBER viewOutlinedDepth NOTIFY dirty) - -public: - - bool viewOutlinedDepth{ false }; - -signals: - void dirty(); -}; - -class DebugOutline { -public: - using Inputs = OutlineFramebufferPointer; - using Config = DebugOutlineConfig; - using JobModel = render::Job::ModelI; - - DebugOutline(); - ~DebugOutline(); - - void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); - -private: - - const gpu::PipelinePointer& getDebugPipeline(); - - gpu::PipelinePointer _debugPipeline; - int _geometryId{ 0 }; - bool _isDisplayDepthEnabled{ false }; -}; - -class DrawOutlineTask { -public: - using Inputs = render::VaryingSet5; - using Config = render::Task::Config; - using JobModel = render::Task::ModelI; - - DrawOutlineTask(); - - void configure(const Config& config); - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); - -}; - -#endif // hifi_render_utils_OutlineEffect_h - - diff --git a/libraries/render-utils/src/Outline_shared.slh b/libraries/render-utils/src/Outline_shared.slh deleted file mode 100644 index 902bbd20ad..0000000000 --- a/libraries/render-utils/src/Outline_shared.slh +++ /dev/null @@ -1,28 +0,0 @@ -// glsl / C++ compatible source as interface for Outline -#ifdef __cplusplus -# define VEC2 glm::vec2 -# define VEC3 glm::vec3 -#else -# define VEC2 vec2 -# define VEC3 vec3 -#endif - -struct OutlineParameters -{ - VEC3 _color; - float _intensity; - - VEC2 _size; - float _fillOpacityUnoccluded; - float _fillOpacityOccluded; - - float _threshold; - int _blurKernelSize; - float padding2; - float padding3; -}; - -// <@if 1@> -// Trigger Scribe include -// <@endif@> -// diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 6a3b560167..1f839b25eb 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -35,16 +35,18 @@ #include "TextureCache.h" #include "ZoneRenderer.h" #include "FadeEffect.h" +#include "RenderUtilsLogging.h" #include "AmbientOcclusionEffect.h" #include "AntialiasingEffect.h" #include "ToneMappingEffect.h" #include "SubsurfaceScattering.h" #include "DrawHaze.h" -#include "OutlineEffect.h" +#include "HighlightEffect.h" #include +#include using namespace render; extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); @@ -58,6 +60,18 @@ void RenderDeferredTask::configure(const Config& config) { } +const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, const char* selectionName, + const render::Varying& metas, + const render::Varying& opaques, + const render::Varying& transparents) { + const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), std::string()).asVarying(); + const auto selectedMetas = task.addJob("MetaSelection", selectMetaInput, selectionName); + const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas, std::string()).asVarying(); + const auto selectedMetasAndOpaques = task.addJob("OpaqueSelection", selectMetaAndOpaqueInput, selectionName); + const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, std::string()).asVarying(); + return task.addJob("TransparentSelection", selectItemInput, selectionName); +} + void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { const auto& items = input.get(); auto fadeEffect = DependencyManager::get(); @@ -95,15 +109,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // draw a stencil mask in hidden regions of the framebuffer. task.addJob("PrepareStencil", primaryFramebuffer); - // Select items that need to be outlined - const auto selectionName = "contextOverlayHighlightList"; - const auto selectMetaInput = SelectItems::Inputs(metas, Varying()).asVarying(); - const auto selectedMetas = task.addJob("PassTestMetaSelection", selectMetaInput, selectionName); - const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas).asVarying(); - const auto selectedMetasAndOpaques = task.addJob("PassTestOpaqueSelection", selectMetaAndOpaqueInput, selectionName); - const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques).asVarying(); - const auto selectedItems = task.addJob("PassTestTransparentSelection", selectItemInput, selectionName); - // Render opaque objects in DeferredBuffer const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).asVarying(); task.addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumber); @@ -178,10 +183,15 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer).asVarying(); task.addJob("ToneMapping", toneMappingInputs); - const auto outlineRangeTimer = task.addJob("BeginOutlineRangeTimer", "Outline"); - const auto outlineInputs = DrawOutlineTask::Inputs(selectedItems, shapePlumber, deferredFramebuffer, primaryFramebuffer, deferredFrameTransform).asVarying(); - task.addJob("DrawOutline", outlineInputs); - task.addJob("EndOutlineRangeTimer", outlineRangeTimer); + const auto outlineRangeTimer = task.addJob("BeginHighlightRangeTimer", "Highlight"); + // Select items that need to be outlined + const auto selectionBaseName = "contextOverlayHighlightList"; + const auto selectedItems = addSelectItemJobs(task, selectionBaseName, metas, opaques, transparents); + + const auto outlineInputs = DrawHighlightTask::Inputs(items.get0(), deferredFramebuffer, primaryFramebuffer, deferredFrameTransform).asVarying(); + task.addJob("DrawHighlight", outlineInputs); + + task.addJob("HighlightRangeTimer", outlineRangeTimer); { // DEbug the bounds of the rendered items, still look at the zbuffer task.addJob("DrawMetaBounds", metas); @@ -190,6 +200,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawLightBounds", lights); task.addJob("DrawZones", zones); + + // Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true + task.addJob("DrawSelectionBounds", selectedItems); } // Layered Overlays @@ -236,9 +249,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren } task.addJob("DrawZoneStack", deferredFrameTransform); - - // Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true - task.addJob("DrawSelectionBounds", selectedItems); } // AA job to be revisited @@ -454,6 +464,7 @@ void Blit::run(const RenderContextPointer& renderContext, const gpu::Framebuffer auto blitFbo = renderArgs->_blitFramebuffer; if (!blitFbo) { + qCWarning(renderutils) << "Blit::run - no blit frame buffer."; return; } diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 452420589b..567e7f6ccd 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -198,6 +198,10 @@ public: void configure(const Config& config); void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); +private: + + static const render::Varying addSelectItemJobs(JobModel& task, const char* selectionName, + const render::Varying& metas, const render::Varying& opaques, const render::Varying& transparents); }; #endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/SoftAttachmentModel.cpp b/libraries/render-utils/src/SoftAttachmentModel.cpp index 63b18d49b8..63991f9422 100644 --- a/libraries/render-utils/src/SoftAttachmentModel.cpp +++ b/libraries/render-utils/src/SoftAttachmentModel.cpp @@ -43,7 +43,7 @@ void SoftAttachmentModel::updateClusterMatrices() { const FBXGeometry& geometry = getFBXGeometry(); - for (int i = 0; i < _meshStates.size(); i++) { + for (int i = 0; i < (int) _meshStates.size(); i++) { MeshState& state = _meshStates[i]; const FBXMesh& mesh = geometry.meshes.at(i); @@ -60,17 +60,6 @@ void SoftAttachmentModel::updateClusterMatrices() { } glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]); } - - // Once computed the cluster matrices, update the buffer(s) - if (mesh.clusters.size() > 1) { - if (!state.clusterBuffer) { - state.clusterBuffer = std::make_shared(state.clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) state.clusterMatrices.constData()); - } else { - state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), - (const gpu::Byte*) state.clusterMatrices.constData()); - } - } } // post the blender if we're not currently waiting for one to finish diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index 0e0d8b56b3..f71111b64e 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -116,9 +116,9 @@ void PrepareStencil::drawBackground(gpu::State& state) { gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_REPLACE, gpu::State::STENCIL_OP_KEEP)); } -// Pass if this area has NOT been marked as MASK +// Pass if this area has NOT been marked as MASK or anything containing MASK void PrepareStencil::testMask(gpu::State& state) { - state.setStencilTest(true, 0x00, gpu::State::StencilTest(STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, + state.setStencilTest(true, 0x00, gpu::State::StencilTest(STENCIL_MASK, STENCIL_MASK, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); } diff --git a/libraries/render-utils/src/StencilMaskPass.h b/libraries/render-utils/src/StencilMaskPass.h index ddbf4a7ac0..fc258b607a 100644 --- a/libraries/render-utils/src/StencilMaskPass.h +++ b/libraries/render-utils/src/StencilMaskPass.h @@ -49,8 +49,8 @@ public: static void drawMask(gpu::State& state); static void drawBackground(gpu::State& state); - static void testNoAA(gpu::State& state); static void testMask(gpu::State& state); + static void testNoAA(gpu::State& state); static void testBackground(gpu::State& state); static void testShape(gpu::State& state); static void testMaskDrawShape(gpu::State& state); diff --git a/libraries/render-utils/src/UpdateSceneTask.cpp b/libraries/render-utils/src/UpdateSceneTask.cpp index 06e02907f2..e05f28ef0d 100644 --- a/libraries/render-utils/src/UpdateSceneTask.cpp +++ b/libraries/render-utils/src/UpdateSceneTask.cpp @@ -15,6 +15,7 @@ #include "BackgroundStage.h" #include "HazeStage.h" #include +#include #include "DeferredLightingEffect.h" void UpdateSceneTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { @@ -22,6 +23,7 @@ void UpdateSceneTask::build(JobModel& task, const render::Varying& input, render task.addJob("BackgroundStageSetup"); task.addJob("HazeStageSetup"); task.addJob("TransitionStageSetup"); + task.addJob("HighlightStageSetup"); task.addJob("DefaultLightingSetup"); diff --git a/libraries/render/src/render/FilterTask.cpp b/libraries/render/src/render/FilterTask.cpp index 49a9ada91e..20d29f3e5d 100644 --- a/libraries/render/src/render/FilterTask.cpp +++ b/libraries/render/src/render/FilterTask.cpp @@ -57,7 +57,12 @@ void SliceItems::run(const RenderContextPointer& renderContext, const ItemBounds } void SelectItems::run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems) { - auto selection = renderContext->_scene->getSelection(_name); + auto selectionName{ _name }; + if (!inputs.get2().empty()) { + selectionName = inputs.get2(); + } + + auto selection = renderContext->_scene->getSelection(selectionName); const auto& selectedItems = selection.getItems(); const auto& inItems = inputs.get0(); const auto itemsToAppend = inputs[1]; @@ -118,8 +123,9 @@ void MetaToSubItems::run(const RenderContextPointer& renderContext, const ItemBo for (auto idBound : inItems) { auto& item = scene->getItem(idBound.id); - - item.fetchMetaSubItems(outItems); + if (item.exist()) { + item.fetchMetaSubItems(outItems); + } } } @@ -132,8 +138,9 @@ void IDsToBounds::run(const RenderContextPointer& renderContext, const ItemIDs& if (!_disableAABBs) { for (auto id : inItems) { auto& item = scene->getItem(id); - - outItems.emplace_back(ItemBound{ id, item.getBound() }); + if (item.exist()) { + outItems.emplace_back(ItemBound{ id, item.getBound() }); + } } } else { for (auto id : inItems) { diff --git a/libraries/render/src/render/FilterTask.h b/libraries/render/src/render/FilterTask.h index a7180b6cde..9b40728b00 100644 --- a/libraries/render/src/render/FilterTask.h +++ b/libraries/render/src/render/FilterTask.h @@ -113,12 +113,13 @@ namespace render { // Keep items belonging to the job selection class SelectItems { public: - using Inputs = VaryingSet2; + using Inputs = VaryingSet3; using JobModel = Job::ModelIO; std::string _name; + SelectItems() {} SelectItems(const Selection::Name& name) : _name(name) {} - + void run(const RenderContextPointer& renderContext, const Inputs& inputs, ItemBounds& outItems); }; diff --git a/libraries/render/src/render/HighlightStage.cpp b/libraries/render/src/render/HighlightStage.cpp new file mode 100644 index 0000000000..ade3844321 --- /dev/null +++ b/libraries/render/src/render/HighlightStage.cpp @@ -0,0 +1,130 @@ +#include "HighlightStage.h" + +using namespace render; + +std::string HighlightStage::_name("Highlight"); +const HighlightStage::Index HighlightStage::INVALID_INDEX{ render::indexed_container::INVALID_INDEX }; + +HighlightStage::Index HighlightStage::addHighlight(const std::string& selectionName, const HighlightStyle& style) { + Highlight outline{ selectionName, style }; + Index id; + + id = _highlights.newElement(outline); + _activeHighlightIds.push_back(id); + + return id; +} + +void HighlightStage::removeHighlight(Index index) { + HighlightIdList::iterator idIterator = std::find(_activeHighlightIds.begin(), _activeHighlightIds.end(), index); + if (idIterator != _activeHighlightIds.end()) { + _activeHighlightIds.erase(idIterator); + } + if (!_highlights.isElementFreed(index)) { + _highlights.freeElement(index); + } +} + +Index HighlightStage::getHighlightIdBySelection(const std::string& selectionName) const { + for (auto outlineId : _activeHighlightIds) { + const auto& outline = _highlights.get(outlineId); + if (outline._selectionName == selectionName) { + return outlineId; + } + } + return INVALID_INDEX; +} + +const HighlightStyle& HighlightStageConfig::getStyle() const { + auto styleIterator = _styles.find(_selectionName); + if (styleIterator != _styles.end()) { + return styleIterator->second; + } else { + auto insertion = _styles.insert(SelectionStyles::value_type{ _selectionName, HighlightStyle{} }); + return insertion.first->second; + } +} + +HighlightStyle& HighlightStageConfig::editStyle() { + auto styleIterator = _styles.find(_selectionName); + if (styleIterator != _styles.end()) { + return styleIterator->second; + } else { + auto insertion = _styles.insert(SelectionStyles::value_type{ _selectionName, HighlightStyle{} }); + return insertion.first->second; + } +} + +void HighlightStageConfig::setSelectionName(const QString& name) { + _selectionName = name.toStdString(); + emit dirty(); +} + +void HighlightStageConfig::setOutlineSmooth(bool isSmooth) { + editStyle().isOutlineSmooth = isSmooth; + emit dirty(); +} + +void HighlightStageConfig::setColorRed(float value) { + editStyle().color.r = value; + emit dirty(); +} + +void HighlightStageConfig::setColorGreen(float value) { + editStyle().color.g = value; + emit dirty(); +} + +void HighlightStageConfig::setColorBlue(float value) { + editStyle().color.b = value; + emit dirty(); +} + +void HighlightStageConfig::setOutlineWidth(float value) { + editStyle().outlineWidth = value; + emit dirty(); +} + +void HighlightStageConfig::setOutlineIntensity(float value) { + editStyle().outlineIntensity = value; + emit dirty(); +} + +void HighlightStageConfig::setUnoccludedFillOpacity(float value) { + editStyle().unoccludedFillOpacity = value; + emit dirty(); +} + +void HighlightStageConfig::setOccludedFillOpacity(float value) { + editStyle().occludedFillOpacity = value; + emit dirty(); +} + +HighlightStageSetup::HighlightStageSetup() { +} + +void HighlightStageSetup::configure(const Config& config) { + // Copy the styles here but update the stage with the new styles in run to be sure everything is + // thread safe... + _styles = config._styles; +} + +void HighlightStageSetup::run(const render::RenderContextPointer& renderContext) { + auto stage = renderContext->_scene->getStage(HighlightStage::getName()); + if (!stage) { + stage = std::make_shared(); + renderContext->_scene->resetStage(HighlightStage::getName(), stage); + } + + if (!_styles.empty()) { + render::Transaction transaction; + for (const auto& selection : _styles) { + auto& selectionName = selection.first; + auto& selectionStyle = selection.second; + transaction.resetSelectionHighlight(selectionName, selectionStyle); + } + renderContext->_scene->enqueueTransaction(transaction); + _styles.clear(); + } +} + diff --git a/libraries/render/src/render/HighlightStage.h b/libraries/render/src/render/HighlightStage.h new file mode 100644 index 0000000000..b35fff654c --- /dev/null +++ b/libraries/render/src/render/HighlightStage.h @@ -0,0 +1,135 @@ +// +// HighlightStage.h + +// Created by Olivier Prat on 07/07/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 +// + +#ifndef hifi_render_utils_HighlightStage_h +#define hifi_render_utils_HighlightStage_h + +#include "Stage.h" +#include "Engine.h" +#include "IndexedContainer.h" +#include "HighlightStyle.h" + +namespace render { + + // Highlight stage to set up HighlightStyle-related effects + class HighlightStage : public Stage { + public: + + class Highlight { + public: + + Highlight(const std::string& selectionName, const HighlightStyle& style) : _selectionName{ selectionName }, _style{ style } { } + + std::string _selectionName; + HighlightStyle _style; + + }; + + static const std::string& getName() { return _name; } + + using Index = render::indexed_container::Index; + static const Index INVALID_INDEX; + using HighlightIdList = render::indexed_container::Indices; + + static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } + + bool checkHighlightId(Index index) const { return _highlights.checkIndex(index); } + + const Highlight& getHighlight(Index index) const { return _highlights.get(index); } + Highlight& editHighlight(Index index) { return _highlights.edit(index); } + + Index addHighlight(const std::string& selectionName, const HighlightStyle& style = HighlightStyle()); + Index getHighlightIdBySelection(const std::string& selectionName) const; + void removeHighlight(Index index); + + HighlightIdList::iterator begin() { return _activeHighlightIds.begin(); } + HighlightIdList::iterator end() { return _activeHighlightIds.end(); } + + private: + + using Highlights = render::indexed_container::IndexedVector; + + static std::string _name; + + Highlights _highlights; + HighlightIdList _activeHighlightIds; + }; + using HighlightStagePointer = std::shared_ptr; + + class HighlightStageConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(QString selectionName READ getSelectionName WRITE setSelectionName NOTIFY dirty) + Q_PROPERTY(bool isOutlineSmooth READ isOutlineSmooth WRITE setOutlineSmooth NOTIFY dirty) + Q_PROPERTY(float colorR READ getColorRed WRITE setColorRed NOTIFY dirty) + Q_PROPERTY(float colorG READ getColorGreen WRITE setColorGreen NOTIFY dirty) + Q_PROPERTY(float colorB READ getColorBlue WRITE setColorBlue NOTIFY dirty) + Q_PROPERTY(float outlineWidth READ getOutlineWidth WRITE setOutlineWidth NOTIFY dirty) + Q_PROPERTY(float outlineIntensity READ getOutlineIntensity WRITE setOutlineIntensity NOTIFY dirty) + Q_PROPERTY(float unoccludedFillOpacity READ getUnoccludedFillOpacity WRITE setUnoccludedFillOpacity NOTIFY dirty) + Q_PROPERTY(float occludedFillOpacity READ getOccludedFillOpacity WRITE setOccludedFillOpacity NOTIFY dirty) + + public: + + using SelectionStyles = std::map; + + QString getSelectionName() const { return QString(_selectionName.c_str()); } + void setSelectionName(const QString& name); + + bool isOutlineSmooth() const { return getStyle().isOutlineSmooth; } + void setOutlineSmooth(bool isSmooth); + + float getColorRed() const { return getStyle().color.r; } + void setColorRed(float value); + + float getColorGreen() const { return getStyle().color.g; } + void setColorGreen(float value); + + float getColorBlue() const { return getStyle().color.b; } + void setColorBlue(float value); + + float getOutlineWidth() const { return getStyle().outlineWidth; } + void setOutlineWidth(float value); + + float getOutlineIntensity() const { return getStyle().outlineIntensity; } + void setOutlineIntensity(float value); + + float getUnoccludedFillOpacity() const { return getStyle().unoccludedFillOpacity; } + void setUnoccludedFillOpacity(float value); + + float getOccludedFillOpacity() const { return getStyle().occludedFillOpacity; } + void setOccludedFillOpacity(float value); + + std::string _selectionName{ "contextOverlayHighlightList" }; + mutable SelectionStyles _styles; + + const HighlightStyle& getStyle() const; + HighlightStyle& editStyle(); + + signals: + void dirty(); + }; + + class HighlightStageSetup { + public: + using Config = HighlightStageConfig; + using JobModel = render::Job::Model; + + HighlightStageSetup(); + + void configure(const Config& config); + void run(const RenderContextPointer& renderContext); + + protected: + + HighlightStageConfig::SelectionStyles _styles; + }; + +} +#endif // hifi_render_utils_HighlightStage_h diff --git a/libraries/render/src/render/HighlightStyle.h b/libraries/render/src/render/HighlightStyle.h new file mode 100644 index 0000000000..6e7373c78b --- /dev/null +++ b/libraries/render/src/render/HighlightStyle.h @@ -0,0 +1,38 @@ +// +// HighlightStyle.h + +// Created by Olivier Prat on 11/06/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 +// + +#ifndef hifi_render_utils_HighlightStyle_h +#define hifi_render_utils_HighlightStyle_h + +#include + +#include + +namespace render { + + // This holds the configuration for a particular outline style + class HighlightStyle { + public: + + bool isFilled() const { + return unoccludedFillOpacity > 5e-3f || occludedFillOpacity > 5e-3f; + } + + glm::vec3 color{ 1.f, 0.7f, 0.2f }; + float outlineWidth{ 2.0f }; + float outlineIntensity{ 0.9f }; + float unoccludedFillOpacity{ 0.0f }; + float occludedFillOpacity{ 0.0f }; + bool isOutlineSmooth{ false }; + }; + +} + +#endif // hifi_render_utils_HighlightStyle_h \ No newline at end of file diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 714be2a8c6..88e25b6d27 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -14,6 +14,7 @@ #include #include "Logging.h" #include "TransitionStage.h" +#include "HighlightStage.h" using namespace render; @@ -54,6 +55,18 @@ void Transaction::resetSelection(const Selection& selection) { _resetSelections.emplace_back(selection); } +void Transaction::resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style) { + _highlightResets.emplace_back(HighlightReset{ selectionName, style }); +} + +void Transaction::removeHighlightFromSelection(const std::string& selectionName) { + _highlightRemoves.emplace_back(selectionName); +} + +void Transaction::querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func) { + _highlightQueries.emplace_back(HighlightQuery{ selectionName, func }); +} + void Transaction::merge(const Transaction& transaction) { _resetItems.insert(_resetItems.end(), transaction._resetItems.begin(), transaction._resetItems.end()); _removedItems.insert(_removedItems.end(), transaction._removedItems.begin(), transaction._removedItems.end()); @@ -62,6 +75,9 @@ void Transaction::merge(const Transaction& transaction) { _addedTransitions.insert(_addedTransitions.end(), transaction._addedTransitions.begin(), transaction._addedTransitions.end()); _queriedTransitions.insert(_queriedTransitions.end(), transaction._queriedTransitions.begin(), transaction._queriedTransitions.end()); _reAppliedTransitions.insert(_reAppliedTransitions.end(), transaction._reAppliedTransitions.begin(), transaction._reAppliedTransitions.end()); + _highlightResets.insert(_highlightResets.end(), transaction._highlightResets.begin(), transaction._highlightResets.end()); + _highlightRemoves.insert(_highlightRemoves.end(), transaction._highlightRemoves.begin(), transaction._highlightRemoves.end()); + _highlightQueries.insert(_highlightQueries.end(), transaction._highlightQueries.begin(), transaction._highlightQueries.end()); } @@ -176,6 +192,10 @@ void Scene::processTransactionFrame(const Transaction& transaction) { // resets and potential NEW items resetSelections(transaction._resetSelections); } + + resetHighlights(transaction._highlightResets); + removeHighlights(transaction._highlightRemoves); + queryHighlights(transaction._highlightQueries); } void Scene::resetItems(const Transaction::Resets& transactions) { @@ -316,6 +336,50 @@ void Scene::queryTransitionItems(const Transaction::TransitionQueries& transacti } } +void Scene::resetHighlights(const Transaction::HighlightResets& transactions) { + auto outlineStage = getStage(HighlightStage::getName()); + + for (auto& transaction : transactions) { + const auto& selectionName = std::get<0>(transaction); + const auto& newStyle = std::get<1>(transaction); + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); + + if (HighlightStage::isIndexInvalid(outlineId)) { + outlineStage->addHighlight(selectionName, newStyle); + } else { + outlineStage->editHighlight(outlineId)._style = newStyle; + } + } +} + +void Scene::removeHighlights(const Transaction::HighlightRemoves& transactions) { + auto outlineStage = getStage(HighlightStage::getName()); + + for (auto& selectionName : transactions) { + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); + + if (!HighlightStage::isIndexInvalid(outlineId)) { + outlineStage->removeHighlight(outlineId); + } + } +} + +void Scene::queryHighlights(const Transaction::HighlightQueries& transactions) { + auto outlineStage = getStage(HighlightStage::getName()); + + for (auto& transaction : transactions) { + const auto& selectionName = std::get<0>(transaction); + const auto& func = std::get<1>(transaction); + auto outlineId = outlineStage->getHighlightIdBySelection(selectionName); + + if (!HighlightStage::isIndexInvalid(outlineId)) { + func(&outlineStage->editHighlight(outlineId)._style); + } else { + func(nullptr); + } + } +} + void Scene::collectSubItems(ItemID parentId, ItemIDs& subItems) const { // Access the true item auto& item = _items[parentId]; @@ -362,7 +426,7 @@ void Scene::resetItemTransition(ItemID itemId) { } } -// THis fucntion is thread safe +// This function is thread safe Selection Scene::getSelection(const Selection::Name& name) const { std::unique_lock lock(_selectionsMutex); auto found = _selections.find(name); @@ -373,6 +437,17 @@ Selection Scene::getSelection(const Selection::Name& name) const { } } +// This function is thread safe +bool Scene::isSelectionEmpty(const Selection::Name& name) const { + std::unique_lock lock(_selectionsMutex); + auto found = _selections.find(name); + if (found == _selections.end()) { + return false; + } else { + return (*found).second.isEmpty(); + } +} + void Scene::resetSelections(const Transaction::SelectionResets& transactions) { for (auto selection : transactions) { auto found = _selections.find(selection.getName()); diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index fef2077897..af6204acb4 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -17,6 +17,7 @@ #include "Stage.h" #include "Selection.h" #include "Transition.h" +#include "HighlightStyle.h" namespace render { @@ -37,6 +38,7 @@ class Transaction { public: typedef std::function TransitionQueryFunc; + typedef std::function SelectionHighlightQueryFunc; Transaction() {} ~Transaction() {} @@ -61,6 +63,10 @@ public: // Selection transactions void resetSelection(const Selection& selection); + void resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style = HighlightStyle()); + void removeHighlightFromSelection(const std::string& selectionName); + void querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func); + void merge(const Transaction& transaction); // Checkers if there is work to do when processing the transaction @@ -75,6 +81,9 @@ protected: using TransitionQuery = std::tuple; using TransitionReApply = ItemID; using SelectionReset = Selection; + using HighlightReset = std::tuple; + using HighlightRemove = std::string; + using HighlightQuery = std::tuple; using Resets = std::vector; using Removes = std::vector; @@ -83,6 +92,9 @@ protected: using TransitionQueries = std::vector; using TransitionReApplies = std::vector; using SelectionResets = std::vector; + using HighlightResets = std::vector; + using HighlightRemoves = std::vector; + using HighlightQueries = std::vector; Resets _resetItems; Removes _removedItems; @@ -91,6 +103,9 @@ protected: TransitionQueries _queriedTransitions; TransitionReApplies _reAppliedTransitions; SelectionResets _resetSelections; + HighlightResets _highlightResets; + HighlightRemoves _highlightRemoves; + HighlightQueries _highlightQueries; }; typedef std::queue TransactionQueue; @@ -102,6 +117,7 @@ typedef std::queue TransactionQueue; // Items are notified accordingly on any update message happening class Scene { public: + Scene(glm::vec3 origin, float size); ~Scene(); @@ -127,6 +143,10 @@ public: // Thread safe Selection getSelection(const Selection::Name& name) const; + // Check if a particular selection is empty (returns true if doesn't exist) + // Thread safe + bool isSelectionEmpty(const Selection::Name& name) const; + // This next call are NOT threadsafe, you have to call them from the correct thread to avoid any potential issues // Access a particular item form its ID @@ -187,6 +207,9 @@ protected: void transitionItems(const Transaction::TransitionAdds& transactions); void reApplyTransitions(const Transaction::TransitionReApplies& transactions); void queryTransitionItems(const Transaction::TransitionQueries& transactions); + void resetHighlights(const Transaction::HighlightResets& transactions); + void removeHighlights(const Transaction::HighlightRemoves& transactions); + void queryHighlights(const Transaction::HighlightQueries& transactions); void collectSubItems(ItemID parentId, ItemIDs& subItems) const; diff --git a/libraries/render/src/render/SortTask.cpp b/libraries/render/src/render/SortTask.cpp index 5b7ead4b6a..00146e393d 100644 --- a/libraries/render/src/render/SortTask.cpp +++ b/libraries/render/src/render/SortTask.cpp @@ -74,9 +74,13 @@ void render::depthSortItems(const RenderContextPointer& renderContext, bool fron std::sort(itemBoundSorts.begin(), itemBoundSorts.end(), backToFrontSort); } - // Finally once sorted result to a list of itemID + // Finally once sorted result to a list of itemID and keep uniques + render::ItemID previousID = Item::INVALID_ITEM_ID; for (auto& item : itemBoundSorts) { - outItems.emplace_back(ItemBound(item._id, item._bounds)); + if (item._id != previousID) { + outItems.emplace_back(ItemBound(item._id, item._bounds)); + previousID = item._id; + } } } diff --git a/libraries/render/src/render/TransitionStage.cpp b/libraries/render/src/render/TransitionStage.cpp index 33ef829c64..9ddc72f4db 100644 --- a/libraries/render/src/render/TransitionStage.cpp +++ b/libraries/render/src/render/TransitionStage.cpp @@ -5,6 +5,7 @@ using namespace render; std::string TransitionStage::_name("Transition"); +const TransitionStage::Index TransitionStage::INVALID_INDEX{ indexed_container::INVALID_INDEX }; TransitionStage::Index TransitionStage::addTransition(ItemID itemId, Transition::Type type, ItemID boundId) { Transition transition; diff --git a/libraries/render/src/render/TransitionStage.h b/libraries/render/src/render/TransitionStage.h index 226d531d8b..1d10a5dedb 100644 --- a/libraries/render/src/render/TransitionStage.h +++ b/libraries/render/src/render/TransitionStage.h @@ -25,7 +25,7 @@ namespace render { static const std::string& getName() { return _name; } using Index = indexed_container::Index; - static const Index INVALID_INDEX{ indexed_container::INVALID_INDEX }; + static const Index INVALID_INDEX; using TransitionIdList = indexed_container::Indices; static bool isIndexInvalid(Index index) { return index == INVALID_INDEX; } diff --git a/libraries/script-engine/src/SceneScriptingInterface.cpp b/libraries/script-engine/src/SceneScriptingInterface.cpp index aa22eaa2d1..3883b948df 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.cpp +++ b/libraries/script-engine/src/SceneScriptingInterface.cpp @@ -129,101 +129,6 @@ QString SceneScripting::Stage::getBackgroundMode() const { }; } -void SceneScripting::Stage::setHazeMode(const QString& mode) { - if (mode == QString("haze off")) { - _skyStage->setHazeMode(model::SunSkyStage::HAZE_OFF); - } else if (mode == QString("haze on")) { - _skyStage->setHazeMode(model::SunSkyStage::HAZE_ON); - } -} - -QString SceneScripting::Stage::getHazeMode() const { - switch (_skyStage->getHazeMode()) { - case model::SunSkyStage::HAZE_OFF: - return QString("haze off"); - case model::SunSkyStage::HAZE_ON: - return QString("haze on"); - default: - return QString("inherit"); - }; -} - -void SceneScripting::Stage::setHazeRange(const float hazeRange) { - _skyStage->setHazeRange(hazeRange); -} -float SceneScripting::Stage::getHazeRange() const { - return _skyStage->getHazeRange(); -} -void SceneScripting::Stage::setHazeColor(const xColor hazeColor) { - _skyStage->setHazeColor(hazeColor); -} -xColor SceneScripting::Stage::getHazeColor() const { - return _skyStage->getHazeColor(); -} -void SceneScripting::Stage::setHazeGlareColor(const xColor hazeGlareColor) { - _skyStage->setHazeGlareColor(hazeGlareColor); -} -xColor SceneScripting::Stage::getHazeGlareColor() const { - return _skyStage->getHazeGlareColor(); -} -void SceneScripting::Stage::setHazeEnableGlare(const bool hazeEnableGlare) { - _skyStage->setHazeEnableGlare(hazeEnableGlare); -} -bool SceneScripting::Stage::getHazeEnableGlare() const { - return _skyStage->getHazeEnableGlare(); -} -void SceneScripting::Stage::setHazeGlareAngle(const float hazeGlareAngle) { - _skyStage->setHazeGlareAngle(hazeGlareAngle); -} -float SceneScripting::Stage::getHazeGlareAngle() const { - return _skyStage->getHazeGlareAngle(); -} - -void SceneScripting::Stage::setHazeAltitudeEffect(const bool hazeAltitudeEffect) { - _skyStage->setHazeAltitudeEffect(hazeAltitudeEffect); -} -bool SceneScripting::Stage::getHazeAltitudeEffect() const { - return _skyStage->getHazeAltitudeEffect(); -} -void SceneScripting::Stage::setHazeCeiling(const float hazeCeiling) { - _skyStage->setHazeCeiling(hazeCeiling); -} -float SceneScripting::Stage::getHazeCeiling() const { - return _skyStage->getHazeCeiling(); -} -void SceneScripting::Stage::setHazeBaseRef(const float hazeBaseRef) { - _skyStage->setHazeBaseRef(hazeBaseRef); -} -float SceneScripting::Stage::getHazeBaseRef() const { - return _skyStage->getHazeBaseRef(); -} - -void SceneScripting::Stage::setHazeBackgroundBlend(const float hazeBackgroundBlend) { - _skyStage->setHazeBackgroundBlend(hazeBackgroundBlend); -} -float SceneScripting::Stage::getHazeBackgroundBlend() const { - return _skyStage->getHazeBackgroundBlend(); -} - -void SceneScripting::Stage::setHazeAttenuateKeyLight(const bool hazeAttenuateKeyLight) { - _skyStage->setHazeAttenuateKeyLight(hazeAttenuateKeyLight); -} -bool SceneScripting::Stage::getHazeAttenuateKeyLight() const { - return _skyStage->getHazeAttenuateKeyLight(); -} -void SceneScripting::Stage::setHazeKeyLightRange(const float hazeKeyLightRange) { - _skyStage->setHazeKeyLightRange(hazeKeyLightRange); -} -float SceneScripting::Stage::getHazeKeyLightRange() const { - return _skyStage->getHazeKeyLightRange(); -} -void SceneScripting::Stage::setHazeKeyLightAltitude(const float hazeKeyLightAltitude) { - _skyStage->setHazeKeyLightAltitude(hazeKeyLightAltitude); -} -float SceneScripting::Stage::getHazeKeyLightAltitude() const { - return _skyStage->getHazeKeyLightAltitude(); -} - SceneScriptingInterface::SceneScriptingInterface() : _stage{ new SceneScripting::Stage{ _skyStage } } { // Let's make sure the sunSkyStage is using a proceduralSkybox _skyStage->setSkybox(model::SkyboxPointer(new ProceduralSkybox())); diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index 791390236a..7bc22eb3e6 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -121,58 +121,6 @@ namespace SceneScripting { void setBackgroundMode(const QString& mode); QString getBackgroundMode() const; - Q_PROPERTY(QString hazeMode READ getHazeMode WRITE setHazeMode) - void setHazeMode(const QString& mode); - QString getHazeMode() const; - - Q_PROPERTY(float hazeRange READ getHazeRange WRITE setHazeRange) - void setHazeRange(float value); - float getHazeRange() const; - - Q_PROPERTY(xColor hazeColor READ getHazeColor WRITE setHazeColor) - void setHazeColor(xColor value); - xColor getHazeColor() const; - - Q_PROPERTY(xColor hazeGlareColor READ getHazeGlareColor WRITE setHazeGlareColor) - void setHazeGlareColor(xColor value); - xColor getHazeGlareColor() const; - - Q_PROPERTY(bool hazeEnableGlare READ getHazeEnableGlare WRITE setHazeEnableGlare) - void setHazeEnableGlare(bool value); - bool getHazeEnableGlare() const; - - Q_PROPERTY(float hazeGlareAngle READ getHazeGlareAngle WRITE setHazeGlareAngle) - void setHazeGlareAngle(float value); - float getHazeGlareAngle() const; - - Q_PROPERTY(bool hazeAltitudeEffect READ getHazeAltitudeEffect WRITE setHazeAltitudeEffect) - void setHazeAltitudeEffect(bool value); - bool getHazeAltitudeEffect() const; - - Q_PROPERTY(float hazeCeiling READ getHazeCeiling WRITE setHazeCeiling) - void setHazeCeiling(float value); - float getHazeCeiling() const; - - Q_PROPERTY(float hazeBaseRef READ getHazeBaseRef WRITE setHazeBaseRef) - void setHazeBaseRef(float value); - float getHazeBaseRef() const; - - Q_PROPERTY(float hazeBackgroundBlend READ getHazeBackgroundBlend WRITE setHazeBackgroundBlend) - void setHazeBackgroundBlend(float value); - float getHazeBackgroundBlend() const; - - Q_PROPERTY(bool hazeAttenuateKeyLight READ getHazeAttenuateKeyLight WRITE setHazeAttenuateKeyLight) - void setHazeAttenuateKeyLight(bool value); - bool getHazeAttenuateKeyLight() const; - - Q_PROPERTY(float hazeKeyLightRange READ getHazeKeyLightRange WRITE setHazeKeyLightRange) - void setHazeKeyLightRange(float value); - float getHazeKeyLightRange() const; - - Q_PROPERTY(float hazeKeyLightAltitude READ getHazeKeyLightAltitude WRITE setHazeKeyLightAltitude) - void setHazeKeyLightAltitude(float value); - float getHazeKeyLightAltitude() const; - protected: model::SunSkyStagePointer _skyStage; LocationPointer _location; diff --git a/libraries/shared/src/DoubleHashKey.cpp b/libraries/shared/src/DoubleHashKey.cpp deleted file mode 100644 index ded2f073eb..0000000000 --- a/libraries/shared/src/DoubleHashKey.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// -// DoubleHashKey.cpp -// libraries/shared/src -// -// Created by Andrew Meadows 2014.11.02 -// 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 "DoubleHashKey.h" - -const uint32_t NUM_PRIMES = 64; -const uint32_t PRIMES[] = { - 4194301U, 4194287U, 4194277U, 4194271U, 4194247U, 4194217U, 4194199U, 4194191U, - 4194187U, 4194181U, 4194173U, 4194167U, 4194143U, 4194137U, 4194131U, 4194107U, - 4194103U, 4194023U, 4194011U, 4194007U, 4193977U, 4193971U, 4193963U, 4193957U, - 4193939U, 4193929U, 4193909U, 4193869U, 4193807U, 4193803U, 4193801U, 4193789U, - 4193759U, 4193753U, 4193743U, 4193701U, 4193663U, 4193633U, 4193573U, 4193569U, - 4193551U, 4193549U, 4193531U, 4193513U, 4193507U, 4193459U, 4193447U, 4193443U, - 4193417U, 4193411U, 4193393U, 4193389U, 4193381U, 4193377U, 4193369U, 4193359U, - 4193353U, 4193327U, 4193309U, 4193303U, 4193297U, 4193279U, 4193269U, 4193263U -}; - -uint32_t DoubleHashKey::hashFunction(uint32_t value, uint32_t primeIndex) { - uint32_t hash = PRIMES[primeIndex % NUM_PRIMES] * (value + 1U); - hash += ~(hash << 15); - hash ^= (hash >> 10); - hash += (hash << 3); - hash ^= (hash >> 6); - hash += ~(hash << 11); - return hash ^ (hash >> 16); -} - -uint32_t DoubleHashKey::hashFunction2(uint32_t value) { - uint32_t hash = 0x811c9dc5U; - for (uint32_t i = 0; i < 4; i++ ) { - uint32_t byte = (value << (i * 8)) >> (24 - i * 8); - hash = ( hash ^ byte ) * 0x01000193U; - } - return hash; -} - -void DoubleHashKey::computeHash(uint32_t value, uint32_t primeIndex) { - _hash = DoubleHashKey::hashFunction(value, primeIndex); - _hash2 = DoubleHashKey::hashFunction2(value); -} diff --git a/libraries/shared/src/DoubleHashKey.h b/libraries/shared/src/DoubleHashKey.h deleted file mode 100644 index ca92a7197f..0000000000 --- a/libraries/shared/src/DoubleHashKey.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// DoubleHashKey.h -// libraries/shared/src -// -// Created by Andrew Meadows 2014.11.02 -// 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 -// - -#ifndef hifi_DoubleHashKey_h -#define hifi_DoubleHashKey_h - -#include - -// DoubleHashKey for use with btHashMap -class DoubleHashKey { -public: - static uint32_t hashFunction(uint32_t value, uint32_t primeIndex); - static uint32_t hashFunction2(uint32_t value); - - DoubleHashKey() : _hash(0), _hash2(0) { } - - DoubleHashKey(uint32_t value, uint32_t primeIndex = 0) : - _hash(hashFunction(value, primeIndex)), - _hash2(hashFunction2(value)) { - } - - void clear() { _hash = 0; _hash2 = 0; } - bool isNull() const { return _hash == 0 && _hash2 == 0; } - - bool equals(const DoubleHashKey& other) const { - return _hash == other._hash && _hash2 == other._hash2; - } - - void computeHash(uint32_t value, uint32_t primeIndex = 0); - uint32_t getHash() const { return _hash; } - uint32_t getHash2() const { return _hash2; } - - void setHash(uint32_t hash) { _hash = hash; } - void setHash2(uint32_t hash2) { _hash2 = hash2; } - -private: - uint32_t _hash; - uint32_t _hash2; -}; - -#endif // hifi_DoubleHashKey_h diff --git a/libraries/shared/src/HashKey.cpp b/libraries/shared/src/HashKey.cpp new file mode 100644 index 0000000000..488eccb1bf --- /dev/null +++ b/libraries/shared/src/HashKey.cpp @@ -0,0 +1,67 @@ +// +// HashKey.cpp +// libraries/shared/src +// +// Created by Andrew Meadows 2017.10.25 +// 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 "HashKey.h" + +#include "NumericalConstants.h" + + +const uint8_t NUM_PRIMES = 64; +const uint64_t PRIMES[] = { + 4194301UL, 4194287UL, 4194277UL, 4194271UL, 4194247UL, 4194217UL, 4194199UL, 4194191UL, + 4194187UL, 4194181UL, 4194173UL, 4194167UL, 4194143UL, 4194137UL, 4194131UL, 4194107UL, + 4194103UL, 4194023UL, 4194011UL, 4194007UL, 4193977UL, 4193971UL, 4193963UL, 4193957UL, + 4193939UL, 4193929UL, 4193909UL, 4193869UL, 4193807UL, 4193803UL, 4193801UL, 4193789UL, + 4193759UL, 4193753UL, 4193743UL, 4193701UL, 4193663UL, 4193633UL, 4193573UL, 4193569UL, + 4193551UL, 4193549UL, 4193531UL, 4193513UL, 4193507UL, 4193459UL, 4193447UL, 4193443UL, + 4193417UL, 4193411UL, 4193393UL, 4193389UL, 4193381UL, 4193377UL, 4193369UL, 4193359UL, + 4193353UL, 4193327UL, 4193309UL, 4193303UL, 4193297UL, 4193279UL, 4193269UL, 4193263UL +}; + + +// this hash function inspired by Squirrel Eiserloh's GDC2017 talk: "Noise-Based RNG" +uint64_t squirrel3_64(uint64_t data, uint8_t primeIndex) { + constexpr uint64_t BIT_NOISE1 = 2760725261486592643UL; + constexpr uint64_t BIT_NOISE2 = 6774464464027632833UL; + constexpr uint64_t BIT_NOISE3 = 5545331650366059883UL; + + // blend prime numbers into the hash to prevent dupes + // when hashing the same set of numbers in a different order + uint64_t hash = PRIMES[primeIndex % NUM_PRIMES] * data; + hash *= BIT_NOISE1; + hash ^= (hash >> 16); + hash += BIT_NOISE2; + hash ^= (hash << 16); + hash *= BIT_NOISE3; + return hash ^ (hash >> 16); +} + +constexpr float QUANTIZED_VALUES_PER_METER = 250.0f; + +// static +float HashKey::getNumQuantizedValuesPerMeter() { + return QUANTIZED_VALUES_PER_METER; +} + +void HashKey::hashUint64(uint64_t data) { + _hash += squirrel3_64(data, ++_hashCount); +} + +void HashKey::hashFloat(float data) { + _hash += squirrel3_64((uint64_t)((int64_t)(data * QUANTIZED_VALUES_PER_METER)), ++_hashCount); +} + +void HashKey::hashVec3(const glm::vec3& data) { + _hash += squirrel3_64((uint64_t)((int64_t)(data[0] * QUANTIZED_VALUES_PER_METER)), ++_hashCount); + _hash *= squirrel3_64((uint64_t)((int64_t)(data[1] * QUANTIZED_VALUES_PER_METER)), ++_hashCount); + _hash ^= squirrel3_64((uint64_t)((int64_t)(data[2] * QUANTIZED_VALUES_PER_METER)), ++_hashCount); +} + diff --git a/libraries/shared/src/HashKey.h b/libraries/shared/src/HashKey.h new file mode 100644 index 0000000000..5fce182084 --- /dev/null +++ b/libraries/shared/src/HashKey.h @@ -0,0 +1,52 @@ +// +// HashKey.h +// libraries/shared/src +// +// Created by Andrew Meadows 2017.10.25 +// 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 +// + +#ifndef hifi_HashKey_h +#define hifi_HashKey_h + +#include +#include + +#include + + +// HashKey for use with btHashMap which requires a particular API for its keys. In particular it +// requires its Key to implement these methods: +// +// bool Key::equals() +// int32_t Key::getHash() +// +// The important thing about the HashKey implementation is that while getHash() returns 32-bits, +// internally HashKey stores a 64-bit hash which is used for the equals() comparison. This allows +// btHashMap to insert "dupe" 32-bit keys to different "values". + +class HashKey { +public: + static float getNumQuantizedValuesPerMeter(); + + // These two methods are required by btHashMap. + bool equals(const HashKey& other) const { return _hash == other._hash; } + int32_t getHash() const { return (int32_t)((uint32_t)_hash); } + + void clear() { _hash = _hashCount = 0; } + bool isNull() const { return _hash == 0 && _hashCount == 0; } + void hashUint64(uint64_t data); + void hashFloat(float data); + void hashVec3(const glm::vec3& data); + + uint64_t getHash64() const { return _hash; } // for debug/test purposes + +private: + uint64_t _hash { 0 }; + uint8_t _hashCount { 0 }; +}; + +#endif // hifi_HashKey_h diff --git a/libraries/shared/src/MovingMinMaxAvg.h b/libraries/shared/src/MovingMinMaxAvg.h index 580baf7317..782b0dc523 100644 --- a/libraries/shared/src/MovingMinMaxAvg.h +++ b/libraries/shared/src/MovingMinMaxAvg.h @@ -59,8 +59,12 @@ public: _max = other._max; } double totalSamples = _samples + other._samples; - _average = _average * ((double)_samples / totalSamples) - + other._average * ((double)other._samples / totalSamples); + if (totalSamples > 0) { + _average = _average * ((double)_samples / totalSamples) + + other._average * ((double)other._samples / totalSamples); + } else { + _average = 0.0f; + } _samples += other._samples; } diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 36ce38335a..8cdc4bcf14 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -53,7 +53,7 @@ void ShapeInfo::clear() { _triangleIndices.clear(); _halfExtents = glm::vec3(0.0f); _offset = glm::vec3(0.0f); - _doubleHashKey.clear(); + _hashKey.clear(); _type = SHAPE_TYPE_NONE; } @@ -87,14 +87,14 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString default: break; } - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setBox(const glm::vec3& halfExtents) { _url = ""; _type = SHAPE_TYPE_BOX; setHalfExtents(halfExtents); - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setSphere(float radius) { @@ -102,12 +102,12 @@ void ShapeInfo::setSphere(float radius) { _type = SHAPE_TYPE_SPHERE; radius = glm::max(radius, MIN_HALF_EXTENT); _halfExtents = glm::vec3(radius); - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) { _pointCollection = pointCollection; - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setCapsuleY(float radius, float halfHeight) { @@ -116,12 +116,12 @@ void ShapeInfo::setCapsuleY(float radius, float halfHeight) { radius = glm::max(radius, MIN_HALF_EXTENT); halfHeight = glm::max(halfHeight, 0.0f); _halfExtents = glm::vec3(radius, halfHeight, radius); - _doubleHashKey.clear(); + _hashKey.clear(); } void ShapeInfo::setOffset(const glm::vec3& offset) { _offset = offset; - _doubleHashKey.clear(); + _hashKey.clear(); } uint32_t ShapeInfo::getNumSubShapes() const { @@ -256,119 +256,46 @@ bool ShapeInfo::contains(const glm::vec3& point) const { } } -const DoubleHashKey& ShapeInfo::getHash() const { +const HashKey& ShapeInfo::getHash() const { // NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance. - if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) { - bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET; + if (_hashKey.isNull() && _type != SHAPE_TYPE_NONE) { // The key is not yet cached therefore we must compute it. - // compute hash1 - // TODO?: provide lookup table for hash/hash2 of _type rather than recompute? - uint32_t primeIndex = 0; - _doubleHashKey.computeHash((uint32_t)_type, primeIndex++); - + _hashKey.hashUint64((uint64_t)_type); if (_type != SHAPE_TYPE_SIMPLE_HULL) { - // compute hash1 - uint32_t hash = _doubleHashKey.getHash(); - for (int j = 0; j < 3; ++j) { - // NOTE: 0.49f is used to bump the float up almost half a millimeter - // so the cast to int produces a round() effect rather than a floor() - hash ^= DoubleHashKey::hashFunction( - (uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f), - primeIndex++); - if (useOffset) { - hash ^= DoubleHashKey::hashFunction( - (uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f), - primeIndex++); - } - } - _doubleHashKey.setHash(hash); - - // compute hash2 - hash = _doubleHashKey.getHash2(); - for (int j = 0; j < 3; ++j) { - // NOTE: 0.49f is used to bump the float up almost half a millimeter - // so the cast to int produces a round() effect rather than a floor() - uint32_t floatHash = DoubleHashKey::hashFunction2( - (uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f)); - if (useOffset) { - floatHash ^= DoubleHashKey::hashFunction2( - (uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f)); - } - hash += ~(floatHash << 17); - hash ^= (floatHash >> 11); - hash += (floatHash << 4); - hash ^= (floatHash >> 7); - hash += ~(floatHash << 10); - hash = (hash << 16) | (hash >> 16); - } - _doubleHashKey.setHash2(hash); + _hashKey.hashVec3(_halfExtents); + _hashKey.hashVec3(_offset); } else { - + // TODO: we could avoid hashing all of these points if we were to supply the ShapeInfo with a unique + // descriptive string. Shapes that are uniquely described by their type and URL could just put their + // url in the description. assert(_pointCollection.size() == (size_t)1); const PointList & points = _pointCollection.back(); const int numPoints = (int)points.size(); - uint32_t hash = _doubleHashKey.getHash(); - uint32_t hash2 = _doubleHashKey.getHash2(); - for (int pointIndex = 0; pointIndex < numPoints; ++pointIndex) { - // compute hash1 & 2 - const glm::vec3 &curPoint = points[pointIndex]; - for (int vecCompIndex = 0; vecCompIndex < 3; ++vecCompIndex) { - - // NOTE: 0.49f is used to bump the float up almost half a millimeter - // so the cast to int produces a round() effect rather than a floor() - uint32_t valueToHash = (uint32_t)(curPoint[vecCompIndex] * MILLIMETERS_PER_METER + copysignf(1.0f, curPoint[vecCompIndex]) * 0.49f); - - hash ^= DoubleHashKey::hashFunction(valueToHash, primeIndex++); - uint32_t floatHash = DoubleHashKey::hashFunction2(valueToHash); - - if (useOffset) { - - const uint32_t offsetValToHash = (uint32_t)(_offset[vecCompIndex] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[vecCompIndex])* 0.49f); - - hash ^= DoubleHashKey::hashFunction(offsetValToHash, primeIndex++); - floatHash ^= DoubleHashKey::hashFunction2(offsetValToHash); - } - - hash2 += ~(floatHash << 17); - hash2 ^= (floatHash >> 11); - hash2 += (floatHash << 4); - hash2 ^= (floatHash >> 7); - hash2 += ~(floatHash << 10); - hash2 = (hash2 << 16) | (hash2 >> 16); - } + for (int i = 0; i < numPoints; ++i) { + _hashKey.hashVec3(points[i]); } - - _doubleHashKey.setHash(hash); - _doubleHashKey.setHash2(hash2); } QString url = _url.toString(); if (!url.isEmpty()) { - // fold the urlHash into both parts QByteArray baUrl = url.toLocal8Bit(); uint32_t urlHash = qChecksum(baUrl.data(), baUrl.size()); - _doubleHashKey.setHash(_doubleHashKey.getHash() ^ urlHash); - _doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ urlHash); + _hashKey.hashUint64((uint64_t)urlHash); } - uint32_t numHulls = 0; if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_SIMPLE_COMPOUND) { - numHulls = (uint32_t)_pointCollection.size(); + uint64_t numHulls = (uint64_t)_pointCollection.size(); + _hashKey.hashUint64(numHulls); } else if (_type == SHAPE_TYPE_SIMPLE_HULL) { - numHulls = 1; - } - if (numHulls > 0) { - uint32_t hash = DoubleHashKey::hashFunction(numHulls, primeIndex++); - _doubleHashKey.setHash(_doubleHashKey.getHash() ^ hash); - hash = DoubleHashKey::hashFunction2(numHulls); - _doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ hash); + _hashKey.hashUint64(1); } } - return _doubleHashKey; + return _hashKey; } void ShapeInfo::setHalfExtents(const glm::vec3& halfExtents) { _halfExtents = glm::max(halfExtents, glm::vec3(MIN_HALF_EXTENT)); + _hashKey.clear(); } diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index d658b936a3..069241e29d 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -18,7 +18,7 @@ #include #include -#include "DoubleHashKey.h" +#include "HashKey.h" const float MIN_SHAPE_OFFSET = 0.001f; // offsets less than 1mm will be ignored @@ -89,7 +89,7 @@ public: /// For compound shapes it will only return whether it is inside the bounding box bool contains(const glm::vec3& point) const; - const DoubleHashKey& getHash() const; + const HashKey& getHash() const; protected: void setHalfExtents(const glm::vec3& halfExtents); @@ -99,7 +99,7 @@ protected: TriangleIndices _triangleIndices; glm::vec3 _halfExtents = glm::vec3(0.0f); glm::vec3 _offset = glm::vec3(0.0f); - mutable DoubleHashKey _doubleHashKey; + mutable HashKey _hashKey; ShapeType _type = SHAPE_TYPE_NONE; }; diff --git a/libraries/shared/src/ViewFrustum.cpp b/libraries/shared/src/ViewFrustum.cpp index 978221e167..dcbfd83ec7 100644 --- a/libraries/shared/src/ViewFrustum.cpp +++ b/libraries/shared/src/ViewFrustum.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -504,16 +505,17 @@ const int hullVertexLookup[MAX_POSSIBLE_COMBINATIONS][MAX_PROJECTED_POLYGON_VERT {6, TOP_RIGHT_NEAR, TOP_RIGHT_FAR, BOTTOM_RIGHT_FAR, BOTTOM_LEFT_FAR, BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, // back, top, left }; -CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { +template +CubeProjectedPolygon ViewFrustum::computeProjectedPolygon(const TBOX& box) const { const glm::vec3& bottomNearRight = box.getCorner(); glm::vec3 topFarLeft = box.calcTopFarLeft(); - int lookUp = ((_position.x < bottomNearRight.x) ) // 1 = right | compute 6-bit - + ((_position.x > topFarLeft.x ) << 1) // 2 = left | code to - + ((_position.y < bottomNearRight.y) << 2) // 4 = bottom | classify camera - + ((_position.y > topFarLeft.y ) << 3) // 8 = top | with respect to - + ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining - + ((_position.z > topFarLeft.z ) << 5); // 32 = back/far | planes + int lookUp = ((_position.x < bottomNearRight.x)) // 1 = right | compute 6-bit + + ((_position.x > topFarLeft.x) << 1) // 2 = left | code to + + ((_position.y < bottomNearRight.y) << 2) // 4 = bottom | classify camera + + ((_position.y > topFarLeft.y) << 3) // 8 = top | with respect to + + ((_position.z < bottomNearRight.z) << 4) // 16 = front/near | the 6 defining + + ((_position.z > topFarLeft.z) << 5); // 32 = back/far | planes int vertexCount = hullVertexLookup[lookUp][0]; //look up number of vertices @@ -524,8 +526,8 @@ CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { bool anyPointsInView = false; // assume the worst! if (vertexCount) { allPointsInView = true; // assume the best! - for(int i = 0; i < vertexCount; i++) { - int vertexNum = hullVertexLookup[lookUp][i+1]; + for (int i = 0; i < vertexCount; i++) { + int vertexNum = hullVertexLookup[lookUp][i + 1]; glm::vec3 point = box.getVertex((BoxVertex)vertexNum); glm::vec2 projectedPoint = projectPoint(point, pointInView); allPointsInView = allPointsInView && pointInView; @@ -538,24 +540,24 @@ CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { // NOTE: This clipping does not improve our overall performance. It basically causes more polygons to // end up in the same quad/half and so the polygon lists get longer, and that's more calls to polygon.occludes() if ( (projectedPolygon.getMaxX() > PolygonClip::RIGHT_OF_CLIPPING_WINDOW ) || - (projectedPolygon.getMaxY() > PolygonClip::TOP_OF_CLIPPING_WINDOW ) || - (projectedPolygon.getMaxX() < PolygonClip::LEFT_OF_CLIPPING_WINDOW ) || - (projectedPolygon.getMaxY() < PolygonClip::BOTTOM_OF_CLIPPING_WINDOW) ) { + (projectedPolygon.getMaxY() > PolygonClip::TOP_OF_CLIPPING_WINDOW ) || + (projectedPolygon.getMaxX() < PolygonClip::LEFT_OF_CLIPPING_WINDOW ) || + (projectedPolygon.getMaxY() < PolygonClip::BOTTOM_OF_CLIPPING_WINDOW) ) { - CoverageRegion::_clippedPolygons++; + CoverageRegion::_clippedPolygons++; - glm::vec2* clippedVertices; - int clippedVertexCount; - PolygonClip::clipToScreen(projectedPolygon.getVertices(), vertexCount, clippedVertices, clippedVertexCount); + glm::vec2* clippedVertices; + int clippedVertexCount; + PolygonClip::clipToScreen(projectedPolygon.getVertices(), vertexCount, clippedVertices, clippedVertexCount); - // Now reset the vertices of our projectedPolygon object - projectedPolygon.setVertexCount(clippedVertexCount); - for(int i = 0; i < clippedVertexCount; i++) { - projectedPolygon.setVertex(i, clippedVertices[i]); - } - delete[] clippedVertices; + // Now reset the vertices of our projectedPolygon object + projectedPolygon.setVertexCount(clippedVertexCount); + for(int i = 0; i < clippedVertexCount; i++) { + projectedPolygon.setVertex(i, clippedVertices[i]); + } + delete[] clippedVertices; - lookUp += PROJECTION_CLIPPED; + lookUp += PROJECTION_CLIPPED; } ***/ } @@ -568,6 +570,97 @@ CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { return projectedPolygon; } +CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AACube& box) const { + return computeProjectedPolygon(box); +} + +CubeProjectedPolygon ViewFrustum::getProjectedPolygon(const AABox& box) const { + return computeProjectedPolygon(box); +} + +bool ViewFrustum::getProjectedRect(const AABox& box, glm::vec2& bottomLeft, glm::vec2& topRight) const { + using Edge = std::pair; + + const int VERTEX_COUNT = 8; + const int EDGE_COUNT = 12; + // In theory, after clipping a box with a plane, only 4 new vertices at max + // should be created but due to potential imprecisions (edge almost parallel to + // near plane for instance) there might be more + const int MAX_VERTEX_COUNT = VERTEX_COUNT + 4 + 2; + + std::array vertices; + std::array boxEdges{ { + Edge{BOTTOM_LEFT_NEAR, BOTTOM_RIGHT_NEAR}, + Edge{TOP_LEFT_NEAR, TOP_RIGHT_NEAR}, + Edge{BOTTOM_LEFT_FAR, BOTTOM_RIGHT_FAR}, + Edge{TOP_LEFT_FAR, TOP_RIGHT_FAR}, + Edge{BOTTOM_LEFT_NEAR, TOP_LEFT_NEAR}, + Edge{BOTTOM_LEFT_FAR, TOP_LEFT_FAR}, + Edge{BOTTOM_RIGHT_NEAR, TOP_RIGHT_NEAR}, + Edge{BOTTOM_RIGHT_FAR, TOP_RIGHT_FAR}, + Edge{BOTTOM_LEFT_NEAR, BOTTOM_LEFT_FAR}, + Edge{TOP_LEFT_NEAR, TOP_LEFT_FAR}, + Edge{BOTTOM_RIGHT_NEAR, BOTTOM_RIGHT_FAR}, + Edge{TOP_RIGHT_NEAR, TOP_RIGHT_FAR} + } }; + std::array distancesToNearPlane; + std::bitset areVerticesInside; + int vertexCount = VERTEX_COUNT; + int i; + + // Clip the hull with the near plane. + const auto& nearPlane = _planes[NEAR_PLANE]; + + for (i = 0; i < VERTEX_COUNT; i++) { + vertices[i] = box.getVertex(static_cast(i)); + distancesToNearPlane[i] = nearPlane.distance(vertices[i]); + } + + for (i = 0; i < EDGE_COUNT; i++) { + const auto& edgeVertexIndices = boxEdges[i]; + const auto& startVertex = vertices[edgeVertexIndices.first]; + const auto& endVertex = vertices[edgeVertexIndices.second]; + float startVertexDistance = distancesToNearPlane[edgeVertexIndices.first]; + float endVertexDistance = distancesToNearPlane[edgeVertexIndices.second]; + bool isStartPointInside = startVertexDistance >= 0.0f; + bool isEndPointInside = endVertexDistance >= 0.0f; + + areVerticesInside.set(edgeVertexIndices.first, isStartPointInside); + areVerticesInside.set(edgeVertexIndices.second, isEndPointInside); + + if (isStartPointInside != isEndPointInside) { + // One of the two vertices is behind the near plane so add a new clipped vertex + // add tag it as projectable. + vertices[vertexCount] = startVertex + (endVertex - startVertex) * (startVertexDistance / (startVertexDistance - endVertexDistance)); + areVerticesInside.set(vertexCount); + vertexCount++; + } + } + + // Project points that are inside + bottomLeft.x = std::numeric_limits::max(); + bottomLeft.y = std::numeric_limits::max(); + topRight.x = -std::numeric_limits::max(); + topRight.y = -std::numeric_limits::max(); + for (i = 0; i < vertexCount; i++) { + if (areVerticesInside[i]) { + bool isPointInside; + auto projectedPoint = projectPoint(vertices[i], isPointInside); + bottomLeft.x = std::min(bottomLeft.x, projectedPoint.x); + bottomLeft.y = std::min(bottomLeft.y, projectedPoint.y); + topRight.x = std::max(topRight.x, projectedPoint.x); + topRight.y = std::max(topRight.y, projectedPoint.y); + } + } + + bottomLeft.x = glm::clamp(bottomLeft.x, -1.0f, 1.0f); + bottomLeft.y = glm::clamp(bottomLeft.y, -1.0f, 1.0f); + topRight.x = glm::clamp(topRight.x, -1.0f, 1.0f); + topRight.y = glm::clamp(topRight.y, -1.0f, 1.0f); + + return areVerticesInside.any(); +} + // Similar strategy to getProjectedPolygon() we use the knowledge of camera position relative to the // axis-aligned voxels to determine which of the voxels vertices must be the furthest. No need for // squares and square-roots. Just compares. diff --git a/libraries/shared/src/ViewFrustum.h b/libraries/shared/src/ViewFrustum.h index d1b88fb2a5..98f666d666 100644 --- a/libraries/shared/src/ViewFrustum.h +++ b/libraries/shared/src/ViewFrustum.h @@ -119,6 +119,8 @@ public: glm::vec2 projectPoint(glm::vec3 point, bool& pointInView) const; CubeProjectedPolygon getProjectedPolygon(const AACube& box) const; + CubeProjectedPolygon getProjectedPolygon(const AABox& box) const; + bool getProjectedRect(const AABox& box, glm::vec2& bottomLeft, glm::vec2& topRight) const; void getFurthestPointFromCamera(const AACube& box, glm::vec3& furthestPoint) const; float distanceToCamera(const glm::vec3& point) const; @@ -169,6 +171,10 @@ private: // Used to project points glm::mat4 _ourModelViewProjectionMatrix; + + template + CubeProjectedPolygon computeProjectedPolygon(const TBOX& box) const; + }; using ViewFrustumPointer = std::shared_ptr; diff --git a/scripts/developer/debugging/debugHaze.qml b/scripts/developer/debugging/debugHaze.qml index e197050efd..5a18f88c79 100644 --- a/scripts/developer/debugging/debugHaze.qml +++ b/scripts/developer/debugging/debugHaze.qml @@ -68,7 +68,7 @@ Item { label: "Haze Color R" integral: false config: root.hazeModel - property: "hazeColorR" + property: "hazeColor.r" max: 1.0 min: 0.0 width: 280 @@ -78,7 +78,7 @@ Item { label: "Haze Color G" integral: false config: root.hazeModel - property: "hazeColorG" + property: "hazeColor.g" max: 1.0 min: 0.0 width: 280 @@ -88,7 +88,7 @@ Item { label: "Haze Color B" integral: false config: root.hazeModel - property: "hazeColorB" + property: "hazeColor.b" max: 1.0 min: 0.0 width: 280 @@ -98,7 +98,7 @@ Item { label: "Sun R" integral: false config: root.hazeModel - property: "hazeDirectionalLightColorR" + property: "hazeGlareColor.r" max: 1.0 min: 0.0 width: 280 @@ -108,7 +108,7 @@ Item { label: "Sun G" integral: false config: root.hazeModel - property: "hazeDirectionalLightColorG" + property: "hazeGlareColor.g" max: 1.0 min: 0.0 width: 280 @@ -118,7 +118,7 @@ Item { label: "Sun B" integral: false config: root.hazeModel - property: "hazeDirectionalLightColorB" + property: "hazeGlareColor.b" max: 1.0 min: 0.0 width: 280 @@ -128,7 +128,7 @@ Item { label: "Sun glare angle" integral: false config: root.hazeModel - property: "hazeDirectionalLightAngle_degs" + property: "hazeGlareAngle_degs" max: 70.0 min: 0.0 width: 280 @@ -148,7 +148,7 @@ Item { label: "BG Blend" integral: false config: root.hazeModel - property: "hazeBackgroundBlendValue" + property: "hazeBackgroundBlend" max: 1.0 min: 0.0 width: 280 @@ -156,9 +156,9 @@ Item { CheckBox { text: "Keylight Attenuation" - checked: root.hazeModel["isDirectionaLightAttenuationActive"] + checked: root.hazeModel["isKeyLightAttenuationActive"] onCheckedChanged: { - root.hazeModel["isDirectionaLightAttenuationActive"] = checked; + root.hazeModel["isKeyLightAttenuationActive"] = checked; } } diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js index b16739b2b8..068efb351b 100644 --- a/scripts/developer/debugging/debugWindow.js +++ b/scripts/developer/debugging/debugWindow.js @@ -10,13 +10,24 @@ (function() { // BEGIN LOCAL_SCOPE +//check if script already running. +var scriptData = ScriptDiscoveryService.getRunning(); +var scripts = scriptData.filter(function (datum) { return datum.name === 'debugWindow.js'; }); +if (scripts.length >= 2) { + //2nd instance of the script is too much + ScriptDiscoveryService.stopScript(scripts[1].url); + return; +} + // Set up the qml ui var qml = Script.resolvePath('debugWindow.qml'); + var window = new OverlayWindow({ title: 'Debug Window', source: qml, width: 400, height: 900, }); + window.setPosition(25, 50); window.closed.connect(function() { Script.stop(); }); diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js new file mode 100644 index 0000000000..5175761978 --- /dev/null +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -0,0 +1,160 @@ +// +// debugHighlight.js +// developer/utilities/render +// +// Olivier Prat, created on 08/08/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 +// + +// Set up the qml ui +var qml = Script.resolvePath('highlight.qml'); +var window = new OverlayWindow({ + title: 'Highlight', + source: qml, + width: 400, + height: 400, +}); +window.closed.connect(function() { Script.stop(); }); + +"use strict"; + +// Created by Sam Gondelman on 9/7/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 + +(function() { // BEGIN LOCAL_SCOPE + +var END_DIMENSIONS = { + x: 0.15, + y: 0.15, + z: 0.15 +}; +var COLOR = {red: 97, green: 247, blue: 255}; +var end = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR, + ignoreRayIntersection: true, + alpha: 1.0, + visible: true +} + +var COLOR2 = {red: 247, green: 97, blue: 255}; +var end2 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR2, + ignoreRayIntersection: true, + alpha: 1.0, + visible: true +} + +var highlightGroupIndex = 0 +var isSelectionAddEnabled = false +var isSelectionEnabled = false +var renderStates = [{name: "test", end: end}]; +var defaultRenderStates = [{name: "test", distance: 20.0, end: end2}]; +var time = 0 + +var ray = LaserPointers.createLaserPointer({ + joint: "Mouse", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS | RayPick.PICK_AVATARS | RayPick.PICK_INVISIBLE | RayPick.PICK_NONCOLLIDABLE, + renderStates: renderStates, + defaultRenderStates: defaultRenderStates, + enabled: false +}); + +function getSelectionName() { + var selectionName = "contextOverlayHighlightList" + + if (highlightGroupIndex>0) { + selectionName += highlightGroupIndex + } + return selectionName +} + +function fromQml(message) { + tokens = message.split(' ') + print("Received '"+message+"' from hightlight.qml") + if (tokens[0]=="highlight") { + highlightGroupIndex = parseInt(tokens[1]) + print("Switching to highlight group "+highlightGroupIndex) + } else if (tokens[0]=="pick") { + isSelectionEnabled = tokens[1]=='true' + print("Ray picking set to "+isSelectionEnabled.toString()) + if (isSelectionEnabled) { + LaserPointers.enableLaserPointer(ray) + } else { + LaserPointers.disableLaserPointer(ray) + } + time = 0 + } else if (tokens[0]=="add") { + isSelectionAddEnabled = tokens[1]=='true' + print("Add to selection set to "+isSelectionAddEnabled.toString()) + if (!isSelectionAddEnabled) { + Selection.clearSelectedItemsList(getSelectionName()) + } + } +} + +window.fromQml.connect(fromQml); + +function cleanup() { + LaserPointers.removeLaserPointer(ray); +} +Script.scriptEnding.connect(cleanup); + +var prevID = 0 +var prevType = "" +var selectedID = 0 +var selectedType = "" +function update(deltaTime) { + + // you have to do this repeatedly because there's a bug but I'll fix it + LaserPointers.setRenderState(ray, "test"); + + var result = LaserPointers.getPrevRayPickResult(ray); + var selectionName = getSelectionName() + + if (isSelectionEnabled && result.type != RayPick.INTERSECTED_NONE) { + time += deltaTime + if (result.objectID != prevID) { + var typeName = "" + if (result.type == RayPick.INTERSECTED_ENTITY) { + typeName = "entity" + } else if (result.type == RayPick.INTERSECTED_OVERLAY) { + typeName = "overlay" + } else if (result.type == RayPick.INTERSECTED_AVATAR) { + typeName = "avatar" + } + + prevID = result.objectID; + prevType = typeName; + time = 0 + } else if (time>1.0 && prevID!=selectedID) { + if (prevID != 0 && !isSelectionAddEnabled) { + Selection.removeFromSelectedItemsList(selectionName, selectedType, selectedID) + } + selectedID = prevID + selectedType = prevType + Selection.addToSelectedItemsList(selectionName, selectedType, selectedID) + print("HIGHLIGHT " + highlightGroupIndex + " picked type: " + result.type + ", id: " + result.objectID); + } + } else { + if (prevID != 0 && !isSelectionAddEnabled) { + Selection.removeFromSelectedItemsList(selectionName, prevType, prevID) + } + prevID = 0 + selectedID = 0 + time = 0 + } +} + +Script.update.connect(update); + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/developer/utilities/render/debugOutline.js b/scripts/developer/utilities/render/debugOutline.js deleted file mode 100644 index e333ab5869..0000000000 --- a/scripts/developer/utilities/render/debugOutline.js +++ /dev/null @@ -1,20 +0,0 @@ -// -// debugOutline.js -// developer/utilities/render -// -// Olivier Prat, created on 08/08/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 -// - -// Set up the qml ui -var qml = Script.resolvePath('outline.qml'); -var window = new OverlayWindow({ - title: 'Outline', - source: qml, - width: 285, - height: 370, -}); -window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/scripts/developer/utilities/render/highlight.qml b/scripts/developer/utilities/render/highlight.qml new file mode 100644 index 0000000000..6be74fcf40 --- /dev/null +++ b/scripts/developer/utilities/render/highlight.qml @@ -0,0 +1,177 @@ +// +// highlight.qml +// developer/utilities/render +// +// Olivier Prat, created on 08/08/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls +import "configSlider" + +Rectangle { + id: root + HifiConstants { id: hifi;} + color: hifi.colors.baseGray; + anchors.margins: hifi.dimensions.contentMargin.x + + property var debugConfig: Render.getConfig("RenderMainView.HighlightDebug") + property var highlightConfig: Render.getConfig("UpdateScene.HighlightStageSetup") + + signal sendToScript(var message); + + Column { + id: col + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + + Row { + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + + HifiControls.CheckBox { + id: debug + text: "View Mask" + checked: root.debugConfig["viewMask"] + onCheckedChanged: { + root.debugConfig["viewMask"] = checked; + } + } + HifiControls.CheckBox { + text: "Hover select" + checked: false + onCheckedChanged: { + sendToScript("pick "+checked.toString()) + } + } + HifiControls.CheckBox { + text: "Add to selection" + checked: false + onCheckedChanged: { + sendToScript("add "+checked.toString()) + } + } + } + + HifiControls.ComboBox { + id: box + width: 350 + z: 999 + editable: true + colorScheme: hifi.colorSchemes.dark + model: [ + "contextOverlayHighlightList", + "highlightList1", + "highlightList2", + "highlightList3", + "highlightList4"] + label: "" + + Timer { + id: postpone + interval: 100; running: false; repeat: false + onTriggered: { paramWidgetLoader.sourceComponent = paramWidgets } + } + onCurrentIndexChanged: { + root.highlightConfig["selectionName"] = model[currentIndex]; + sendToScript("highlight "+currentIndex) + // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component + // by setting the loader source to Null and then recreate it 100ms later + paramWidgetLoader.sourceComponent = undefined; + postpone.interval = 100 + postpone.start() + } + } + + Loader { + id: paramWidgetLoader + sourceComponent: paramWidgets + width: 350 + } + + Component { + id: paramWidgets + + Column { + spacing: 10 + anchors.margins: hifi.dimensions.contentMargin.x + + HifiControls.Label { + text: "Outline" + } + Column { + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + HifiControls.CheckBox { + text: "Smooth" + checked: root.highlightConfig["isOutlineSmooth"] + onCheckedChanged: { + root.highlightConfig["isOutlineSmooth"] = checked; + } + } + Repeater { + model: ["Width:outlineWidth:5.0:0.0", + "Intensity:outlineIntensity:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.highlightConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + } + } + } + + Separator {} + HifiControls.Label { + text: "Color" + } + Repeater { + model: ["Red:colorR:1.0:0.0", + "Green:colorG:1.0:0.0", + "Blue:colorB:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.highlightConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + } + } + + Separator {} + HifiControls.Label { + text: "Fill Opacity" + } + Repeater { + model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0", + "Occluded:occludedFillOpacity:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.highlightConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + } + } + } + } + } +} diff --git a/scripts/developer/utilities/render/highlightPage/HighlightPage.qml b/scripts/developer/utilities/render/highlightPage/HighlightPage.qml new file mode 100644 index 0000000000..5669f90628 --- /dev/null +++ b/scripts/developer/utilities/render/highlightPage/HighlightPage.qml @@ -0,0 +1,116 @@ +// +// highlightPage.qml +// developer/utilities/render +// +// Olivier Prat, created on 08/08/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 +import "../configSlider" +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + +Rectangle { + id: root + property var highlightIndex: 0 + property var drawConfig: Render.getConfig("RenderMainView.HighlightEffect"+highlightIndex) + + HifiConstants { id: hifi;} + anchors.margins: hifi.dimensions.contentMargin.x + + Column { + spacing: 5 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + + HifiControls.CheckBox { + id: glow + text: "Glow" + checked: root.drawConfig["glow"] + onCheckedChanged: { + root.drawConfig["glow"] = checked; + } + } + Repeater { + model: ["Width:width:5.0:0.0", + "Intensity:intensity:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.drawConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + + anchors.left: parent.left + anchors.right: parent.right + } + } + + GroupBox { + title: "Color" + anchors.left: parent.left + anchors.right: parent.right + Column { + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + + Repeater { + model: ["Red:colorR:1.0:0.0", + "Green:colorG:1.0:0.0", + "Blue:colorB:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.drawConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + + anchors.left: parent.left + anchors.right: parent.right + } + } + } + } + + GroupBox { + title: "Fill Opacity" + anchors.left: parent.left + anchors.right: parent.right + Column { + spacing: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + + Repeater { + model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0", + "Occluded:occludedFillOpacity:1.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.drawConfig + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + + anchors.left: parent.left + anchors.right: parent.right + } + } + } + } + } +} diff --git a/scripts/developer/utilities/render/highlightPage/qmldir b/scripts/developer/utilities/render/highlightPage/qmldir new file mode 100644 index 0000000000..bb3de24b84 --- /dev/null +++ b/scripts/developer/utilities/render/highlightPage/qmldir @@ -0,0 +1 @@ +HighlightPage 1.0 HighlightPage.qml \ No newline at end of file diff --git a/scripts/developer/utilities/render/outline.qml b/scripts/developer/utilities/render/outline.qml deleted file mode 100644 index e17f7c1f1c..0000000000 --- a/scripts/developer/utilities/render/outline.qml +++ /dev/null @@ -1,119 +0,0 @@ -// -// outline.qml -// developer/utilities/render -// -// Olivier Prat, created on 08/08/2017. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html -// -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import "configSlider" - -Item { - id: root - property var debugConfig: Render.getConfig("RenderMainView.OutlineDebug") - property var drawConfig: Render.getConfig("RenderMainView.OutlineEffect") - - Column { - spacing: 8 - - CheckBox { - text: "View Outlined Depth" - checked: root.debugConfig["viewOutlinedDepth"] - onCheckedChanged: { - root.debugConfig["viewOutlinedDepth"] = checked; - } - } - CheckBox { - text: "Glow" - checked: root.drawConfig["glow"] - onCheckedChanged: { - root.drawConfig["glow"] = checked; - } - } - ConfigSlider { - label: "Width" - integral: false - config: root.drawConfig - property: "width" - max: 15.0 - min: 0.0 - width: 280 - } - ConfigSlider { - label: "Intensity" - integral: false - config: root.drawConfig - property: "intensity" - max: 1.0 - min: 0.0 - width: 280 - } - - GroupBox { - title: "Color" - width: 280 - Column { - spacing: 8 - - ConfigSlider { - label: "Red" - integral: false - config: root.drawConfig - property: "colorR" - max: 1.0 - min: 0.0 - width: 270 - } - ConfigSlider { - label: "Green" - integral: false - config: root.drawConfig - property: "colorG" - max: 1.0 - min: 0.0 - width: 270 - } - ConfigSlider { - label: "Blue" - integral: false - config: root.drawConfig - property: "colorB" - max: 1.0 - min: 0.0 - width: 270 - } - } - } - - GroupBox { - title: "Fill Opacity" - width: 280 - Column { - spacing: 8 - - ConfigSlider { - label: "Unoccluded" - integral: false - config: root.drawConfig - property: "fillOpacityUnoccluded" - max: 1.0 - min: 0.0 - width: 270 - } - ConfigSlider { - label: "Occluded" - integral: false - config: root.drawConfig - property: "fillOpacityOccluded" - max: 1.0 - min: 0.0 - width: 270 - } - } - } - } -} diff --git a/scripts/developer/utilities/render/statsGPU.qml b/scripts/developer/utilities/render/statsGPU.qml index 6b80f00af3..8d284c11ca 100644 --- a/scripts/developer/utilities/render/statsGPU.qml +++ b/scripts/developer/utilities/render/statsGPU.qml @@ -65,6 +65,13 @@ Item { label: "tone and post", color: "#FF0000" } + , + { + object: Render.getConfig("RenderMainView.OutlineRangeTimer"), + prop: "gpuRunTime", + label: "outline", + color: "#FFFF00" + } ] } PlotPerf { @@ -105,6 +112,13 @@ Item { label: "tone and post", color: "#FF0000" } + , + { + object: Render.getConfig("RenderMainView.OutlineRangeTimer"), + prop: "batchRunTime", + label: "outline", + color: "#FFFF00" + } ] } } diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 565a67e116..7843f3a18a 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -225,28 +225,41 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } function getAttachPointForHotspotFromSettings(hotspot, hand) { + var skeletonModelURL = MyAvatar.skeletonModelURL; var attachPointSettings = getAttachPointSettings(); - var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand"; - var joints = attachPointSettings[hotspot.key]; - if (joints) { - return joints[jointName]; - } else { - return undefined; + var avatarSettingsData = attachPointSettings[skeletonModelURL]; + if (avatarSettingsData) { + var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand"; + var joints = avatarSettingsData[hotspot.key]; + if (joints) { + return joints[jointName]; + } } + return undefined; } function storeAttachPointForHotspotInSettings(hotspot, hand, offsetPosition, offsetRotation) { var attachPointSettings = getAttachPointSettings(); + var skeletonModelURL = MyAvatar.skeletonModelURL; + var avatarSettingsData = attachPointSettings[skeletonModelURL]; + if (!avatarSettingsData) { + avatarSettingsData = {}; + attachPointSettings[skeletonModelURL] = avatarSettingsData; + } var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand"; - var joints = attachPointSettings[hotspot.key]; + var joints = avatarSettingsData[hotspot.key]; if (!joints) { joints = {}; - attachPointSettings[hotspot.key] = joints; + avatarSettingsData[hotspot.key] = joints; } joints[jointName] = [offsetPosition, offsetRotation]; setAttachPointSettings(attachPointSettings); } + function clearAttachPoints() { + setAttachPointSettings({}); + } + function EquipEntity(hand) { this.hand = hand; this.targetEntityID = null; @@ -538,6 +551,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa }; this.endEquipEntity = function () { + this.storeAttachPointInSettings(); Entities.editEntity(this.targetEntityID, { parentID: Uuid.NULL, parentJointIndex: -1 @@ -684,14 +698,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (dropDetected && !this.waitForTriggerRelease && this.triggerSmoothedGrab()) { this.waitForTriggerRelease = true; // store the offset attach points into preferences. - if (this.grabbedHotspot && this.targetEntityID) { - var prefprops = Entities.getEntityProperties(this.targetEntityID, ["localPosition", "localRotation"]); - if (prefprops && prefprops.localPosition && prefprops.localRotation) { - storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, - prefprops.localPosition, prefprops.localRotation); - } - } - this.endEquipEntity(); return makeRunningValues(false, [], []); } @@ -707,6 +713,16 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa return makeRunningValues(true, [this.targetEntityID], []); }; + this.storeAttachPointInSettings = function() { + if (this.grabbedHotspot && this.targetEntityID) { + var prefProps = Entities.getEntityProperties(this.targetEntityID, ["localPosition", "localRotation"]); + if (prefProps && prefProps.localPosition && prefProps.localRotation) { + storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, + prefProps.localPosition, prefProps.localRotation); + } + } + }; + this.cleanup = function () { if (this.targetEntityID) { this.endEquipEntity(); @@ -751,11 +767,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa enableDispatcherModule("LeftEquipEntity", leftEquipEntity); enableDispatcherModule("RightEquipEntity", rightEquipEntity); - this.cleanup = function () { + function cleanup() { leftEquipEntity.cleanup(); rightEquipEntity.cleanup(); disableDispatcherModule("LeftEquipEntity"); disableDispatcherModule("RightEquipEntity"); + clearAttachPoints(); }; - Script.scriptEnding.connect(this.cleanup); + Script.scriptEnding.connect(cleanup); }()); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index e34855d521..7d9a6dc1b5 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -13,12 +13,13 @@ makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, PICK_MAX_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, ensureDynamic, - getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI + getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, Reticle, Overlays, isPointingAtUI, Xform, getEntityParents */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); +Script.include("/~/system/libraries/Xform.js"); (function() { var PICK_WITH_HAND_RAY = true; @@ -113,18 +114,71 @@ Script.include("/~/system/libraries/controllers.js"); ]; var MARGIN = 25; + + function TargetObject(entityID, entityProps) { + this.entityID = entityID; + this.entityProps = entityProps; + this.targetEntityID = null; + this.targetEntityProps = null; + this.previousCollisionStatus = null; + this.madeDynamic = null; + + this.makeDynamic = function() { + if (this.targetEntityID) { + var newProps = { + dynamic: true, + collisionless: true + }; + this.previousCollisionStatus = this.targetEntityProps.collisionless; + Entities.editEntity(this.targetEntityID, newProps); + this.madeDynamic = true; + } + }; + + this.restoreTargetEntityOriginalProps = function() { + if (this.madeDynamic) { + var props = {}; + props.dynamic = false; + props.collisionless = this.previousCollisionStatus; + var zeroVector = {x: 0, y: 0, z:0}; + props.localVelocity = zeroVector; + props.localRotation = zeroVector; + Entities.editEntity(this.targetEntityID, props); + } + }; + + this.getTargetEntity = function() { + var parentPropsLength = this.parentProps.length; + if (parentPropsLength !== 0) { + var targetEntity = { + id: this.parentProps[parentPropsLength - 1].id, + props: this.parentProps[parentPropsLength - 1]}; + this.targetEntityID = targetEntity.id; + this.targetEntityProps = targetEntity.props; + return targetEntity; + } + this.targetEntityID = this.entityID; + this.targetEntityProps = this.entityProps; + return { + id: this.entityID, + props: this.entityProps}; + }; + } + function FarActionGrabEntity(hand) { this.hand = hand; this.grabbedThingID = null; + this.targetObject = null; this.actionID = null; // action this script created... + this.entityToLockOnto = null; this.entityWithContextOverlay = false; this.contextOverlayTimer = false; this.previousCollisionStatus = false; + this.locked = false; this.reticleMinX = MARGIN; this.reticleMaxX; this.reticleMinY = MARGIN; this.reticleMaxY; - this.madeDynamic = false; var ACTION_TTL = 15; // seconds @@ -158,9 +212,25 @@ Script.include("/~/system/libraries/controllers.js"); LaserPointers.enableLaserPointer(laserPointerID); LaserPointers.setRenderState(laserPointerID, mode); if (this.distanceHolding || this.distanceRotating) { - LaserPointers.setLockEndUUID(laserPointerID, this.grabbedThingID, this.grabbedIsOverlay); + if (!this.locked) { + // calculate offset + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, [ + "position", + "rotation" + ]); + var zeroVector = { x: 0, y: 0, z:0, w: 0 }; + var intersection = controllerData.rayPicks[this.hand].intersection; + var intersectionMat = new Xform(zeroVector, intersection); + var modelMat = new Xform(targetProps.rotation, targetProps.position); + var modelMatInv = modelMat.inv(); + var xformMat = Xform.mul(modelMatInv, intersectionMat); + var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos); + LaserPointers.setLockEndUUID(laserPointerID, this.targetObject.entityID, this.grabbedIsOverlay, offsetMat); + this.locked = true; + } } else { LaserPointers.setLockEndUUID(laserPointerID, null, false); + this.locked = false; } }; @@ -339,21 +409,15 @@ Script.include("/~/system/libraries/controllers.js"); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); - - if (this.madeDynamic) { - var props = {}; - props.dynamic = false; - props.collisionless = this.previousCollisionStatus; - props.localVelocity = {x: 0, y: 0, z: 0}; - props.localRotation = {x: 0, y: 0, z: 0}; - Entities.editEntity(this.grabbedThingID, props); - this.madeDynamic = false; + if (this.targetObject) { + this.targetObject.restoreTargetEntityOriginalProps(); } this.actionID = null; this.grabbedThingID = null; + this.targetObject = null; }; - this.updateRecommendedArea = function() { + this.updateRecommendedArea = function() { var dims = Controller.getViewportDimensions(); this.reticleMaxX = dims.x - MARGIN; this.reticleMaxY = dims.y - MARGIN; @@ -503,17 +567,18 @@ Script.include("/~/system/libraries/controllers.js"); "userData", "locked", "type" ]); + this.targetObject = new TargetObject(entityID, targetProps); + this.targetObject.parentProps = getEntityParents(targetProps); if (entityID !== this.entityWithContextOverlay) { this.destroyContextOverlay(); } + var targetEntity = this.targetObject.getTargetEntity(); + entityID = targetEntity.id; + targetProps = targetEntity.props; if (entityIsGrabbable(targetProps)) { if (!entityIsDistanceGrabbable(targetProps)) { - targetProps.dynamic = true; - this.previousCollisionStatus = targetProps.collisionless; - targetProps.collisionless = true; - Entities.editEntity(entityID, targetProps); - this.madeDynamic = true; + this.targetObject.makeDynamic(); } if (!this.distanceRotating) { diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index beb86d0ef4..46b630d023 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -154,6 +154,25 @@ Script.include("/~/system/libraries/controllers.js"); this.showStylus = function() { if (this.stylus) { + var X_ROT_NEG_90 = { x: -0.70710678, y: 0, z: 0, w: 0.70710678 }; + var modelOrientation = Quat.multiply(this.stylusTip.orientation, X_ROT_NEG_90); + var modelOrientationAngles = Quat.safeEulerAngles(modelOrientation); + + var rotation = Overlays.getProperty(this.stylus, "rotation"); + var rotationAngles = Quat.safeEulerAngles(rotation); + + if(!Vec3.withinEpsilon(modelOrientationAngles, rotationAngles, 1)) { + var modelPositionOffset = Vec3.multiplyQbyV(modelOrientation, { x: 0, y: 0, z: MyAvatar.sensorToWorldScale * -WEB_STYLUS_LENGTH / 2 }); + + var updatedStylusProperties = { + position: Vec3.sum(this.stylusTip.position, modelPositionOffset), + rotation: modelOrientation, + dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }), + }; + + Overlays.editOverlay(this.stylus, updatedStylusProperties); + } + return; } diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index a1846e7ad7..eaa15ee3b1 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -315,6 +315,10 @@ Grabber.prototype.pressEvent = function(event) { return; } + if (event.isAlt || event.isMeta) { + return; + } + if (Overlays.getOverlayAtPoint(Reticle.position) > 0) { // the mouse is pointing at an overlay; don't look for entities underneath the overlay. return; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index e76a02b6f5..15f1c2f6c1 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1333,7 +1333,7 @@ function sortSelectedEntities(selected) { return sortedEntities; } -function recursiveDelete(entities, childrenList) { +function recursiveDelete(entities, childrenList, deletedIDs) { var entitiesLength = entities.length; for (var i = 0; i < entitiesLength; i++) { var entityID = entities[i]; @@ -1346,6 +1346,7 @@ function recursiveDelete(entities, childrenList) { properties: initialProperties, children: grandchildrenList }); + deletedIDs.push(entityID); Entities.deleteEntity(entityID); } } @@ -1413,6 +1414,8 @@ function parentSelectedEntities() { } function deleteSelectedEntities() { if (SelectionManager.hasSelection()) { + var deletedIDs = []; + selectedParticleEntityID = null; particleExplorerTool.destroyWebView(); SelectionManager.saveProperties(); @@ -1423,16 +1426,22 @@ function deleteSelectedEntities() { var initialProperties = SelectionManager.savedProperties[entityID]; var children = Entities.getChildrenIDs(entityID); var childList = []; - recursiveDelete(children, childList); + recursiveDelete(children, childList, deletedIDs); savedProperties.push({ entityID: entityID, properties: initialProperties, children: childList }); + deletedIDs.push(entityID); Entities.deleteEntity(entityID); } SelectionManager.clearSelections(); pushCommandForSelections([], savedProperties); + + entityListTool.webView.emitScriptEvent(JSON.stringify({ + type: "deleted", + ids: deletedIDs + })); } } @@ -1533,7 +1542,7 @@ function handeMenuEvent(menuItem) { Window.openFileChanged.connect(onFileOpenChanged); Window.browseAsync("Select Model to Import", "", "*.json"); } else { - Window.promptTextChanged.connect(onFileOpenChanged); + Window.promptTextChanged.connect(onPromptTextChanged); Window.promptAsync("URL of SVO to import", ""); } } else if (menuItem === "Entity List...") { diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 2d5dd35e66..9453b476ee 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -59,6 +59,7 @@ +
@@ -554,59 +555,79 @@
-
+
+ + +
-
-
-
-
-
-
-
+
+
+
+
+
-
+
Haze Color
-
+
-
+
-
+
+
+
+
+
+ + + + +
+
+
+
-
+
Glare Color
-
+
-
+
-
+
-
+
+
+ + + +
+
+
-
+
+
+ + +
-
- - -
@@ -614,12 +635,6 @@ min="-1000" max="50000" step="10">
-
-
- - -
-
diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index ea79750154..7b25e66c67 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -286,7 +286,6 @@ function loaded() { } elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); - refreshEntities(); } document.addEventListener("keydown", function (keyDownEvent) { @@ -362,6 +361,12 @@ function loaded() { updateSelectedEntities(data.selectedIDs); resize(); } + } else if (data.type === "deleted") { + for (i = 0, length = data.ids.length; i < length; i++) { + delete entities[data.ids[i]]; + entityList.remove("id", data.ids[i]); + } + refreshFooter(); } }); setTimeout(refreshEntities, 1000); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index a015eed714..0463ac4172 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -678,14 +678,14 @@ function loaded() { var elZoneHazeModeEnabled = document.getElementById("property-zone-haze-mode-enabled"); var elZoneHazeRange = document.getElementById("property-zone-haze-range"); - var elZoneHazeColor = document.getElementById("property-zone-haze-blend-in-color"); - var elZoneHazeColorRed = document.getElementById("property-zone-haze-blend-in-color-red"); - var elZoneHazeColorGreen = document.getElementById("property-zone-haze-blend-in-color-green"); - var elZoneHazeColorBlue = document.getElementById("property-zone-haze-blend-in-color-blue"); - var elZoneHazeGlareColor = document.getElementById("property-zone-haze-blend-out-color"); - var elZoneHazeGlareColorRed = document.getElementById("property-zone-haze-blend-out-color-red"); - var elZoneHazeGlareColorGreen = document.getElementById("property-zone-haze-blend-out-color-green"); - var elZoneHazeGlareColorBlue = document.getElementById("property-zone-haze-blend-out-color-blue"); + var elZoneHazeColor = document.getElementById("property-zone-haze-color"); + var elZoneHazeColorRed = document.getElementById("property-zone-haze-color-red"); + var elZoneHazeColorGreen = document.getElementById("property-zone-haze-color-green"); + var elZoneHazeColorBlue = document.getElementById("property-zone-haze-color-blue"); + var elZoneHazeGlareColor = document.getElementById("property-zone-haze-glare-color"); + var elZoneHazeGlareColorRed = document.getElementById("property-zone-haze-glare-color-red"); + var elZoneHazeGlareColorGreen = document.getElementById("property-zone-haze-glare-color-green"); + var elZoneHazeGlareColorBlue = document.getElementById("property-zone-haze-glare-color-blue"); var elZoneHazeEnableGlare = document.getElementById("property-zone-haze-enable-light-blend"); var elZonehazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); @@ -1474,15 +1474,15 @@ function loaded() { elZoneHazeRange.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeRange')); - colorPickers.push($('#property-zone-haze-blend-in-color').colpick({ + colorPickers.push($('#property-zone-haze-color').colpick({ colorScheme: 'dark', layout: 'hex', color: '000000', onShow: function(colpick) { - $('#property-zone-haze-blend-in-color').attr('active', 'true'); + $('#property-zone-haze-color').attr('active', 'true'); }, onHide: function(colpick) { - $('#property-zone-haze-blend-in-color').attr('active', 'false'); + $('#property-zone-haze-color').attr('active', 'false'); }, onSubmit: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); @@ -1499,15 +1499,15 @@ function loaded() { elZoneHazeColorGreen.addEventListener('change', zoneHazeColorChangeFunction); elZoneHazeColorBlue.addEventListener('change', zoneHazeColorChangeFunction); - colorPickers.push($('#property-zone-haze-blend-out-color').colpick({ + colorPickers.push($('#property-zone-haze-glare-color').colpick({ colorScheme: 'dark', layout: 'hex', color: '000000', onShow: function(colpick) { - $('#property-zone-haze-blend-out-color').attr('active', 'true'); + $('#property-zone-haze-glare-color').attr('active', 'true'); }, onHide: function(colpick) { - $('#property-zone-haze-blend-out-color').attr('active', 'false'); + $('#property-zone-haze-glare-color').attr('active', 'false'); }, onSubmit: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 8deb5c0bbd..4b127baef8 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -290,11 +290,17 @@ } }); - // change pricing to GET on button hover + // change pricing to GET/BUY on button hover $('body').on('mouseenter', '#price-or-edit .price', function () { var $this = $(this); $this.data('initialHtml', $this.html()); - $this.text('GET'); + + var cost = $(this).parent().siblings().text(); + if (parseInt(cost) > 0) { + $this.text('BUY'); + } else { + $this.text('GET'); + } }); $('body').on('mouseleave', '#price-or-edit .price', function () { diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js index 777504b16d..63b161eb80 100644 --- a/scripts/system/libraries/cloneEntityUtils.js +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -8,8 +8,7 @@ /* global entityIsCloneable:true, getGrabbableData:true, cloneEntity:true, propsAreCloneDynamic:true, Script, propsAreCloneDynamic:true, Entities*/ -Script.include("/~/system/controllers/controllerDispatcherUtils.js"); - +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); // Object assign polyfill if (typeof Object.assign !== 'function') { diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index cd3f1a711f..fb6de0e683 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -35,12 +35,14 @@ propsArePhysical:true, controllerDispatcherPluginsNeedSort:true, projectOntoXYPlane:true, + getChildrenProps:true, projectOntoEntityXYPlane:true, projectOntoOverlayXYPlane:true, entityHasActions:true, ensureDynamic:true, findGroupParent:true, BUMPER_ON_VALUE:true, + getEntityParents:true, findHandChildEntities:true, TEAR_AWAY_DISTANCE:true, TEAR_AWAY_COUNT:true, @@ -306,6 +308,23 @@ findGroupParent = function (controllerData, targetProps) { return targetProps; }; +getEntityParents = function(targetProps) { + var parentProperties = []; + while (targetProps.parentID && + targetProps.parentID !== Uuid.NULL && + Entities.getNestableType(targetProps.parentID) == "entity") { + var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES); + if (!parentProps) { + break; + } + parentProps.id = targetProps.parentID; + targetProps = parentProps; + parentProperties.push(parentProps); + } + + return parentProperties; +}; + findHandChildEntities = function(hand) { // find children of avatar's hand joint diff --git a/tests/octree/src/OctreeTests.cpp b/tests/octree/src/OctreeTests.cpp index 64d68e8e13..81300a1293 100644 --- a/tests/octree/src/OctreeTests.cpp +++ b/tests/octree/src/OctreeTests.cpp @@ -74,7 +74,7 @@ void OctreeTests::propertyFlagsTests() { EntityPropertyFlags props; props.setHasProperty(PROP_VISIBLE); props.setHasProperty(PROP_POSITION); - props.setHasProperty(PROP_RADIUS); + props.setHasProperty(PROP_DIMENSIONS); props.setHasProperty(PROP_MODEL_URL); props.setHasProperty(PROP_COMPOUND_SHAPE_URL); props.setHasProperty(PROP_ROTATION); diff --git a/tests/physics/src/ShapeInfoTests.cpp b/tests/physics/src/ShapeInfoTests.cpp index c6a19084a2..79d0092dc3 100644 --- a/tests/physics/src/ShapeInfoTests.cpp +++ b/tests/physics/src/ShapeInfoTests.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include #include @@ -23,108 +23,78 @@ QTEST_MAIN(ShapeInfoTests) +// Enable this to manually run testHashCollisions +// (NOT a regular unit test; takes ~40 secs to run on an i7) +//#define MANUAL_TEST true + void ShapeInfoTests::testHashFunctions() { #if MANUAL_TEST int maxTests = 10000000; ShapeInfo info; - btHashMap hashes; + btHashMap hashes; - uint32_t bits[32]; - uint32_t masks[32]; - for (int i = 0; i < 32; ++i) { + const int32_t NUM_HASH_BITS = 32; + uint32_t bits[NUM_HASH_BITS]; + uint32_t masks[NUM_HASH_BITS]; + for (int i = 0; i < NUM_HASH_BITS; ++i) { bits[i] = 0; - masks[i] = 1U << i; + masks[i] = 1UL << i; } - float deltaLength = 0.002f; - float endLength = 100.0f; + float deltaLength = 1.0f / (HashKey::getNumQuantizedValuesPerMeter() - 3.0f); + float endLength = 2000.0f * deltaLength; int numSteps = (int)(endLength / deltaLength); int testCount = 0; int numCollisions = 0; btClock timer; - for (int x = 1; x < numSteps && testCount < maxTests; ++x) { - float radiusX = (float)x * deltaLength; + for (int i = 1; i < numSteps && testCount < maxTests; ++i) { + float radiusX = (float)i * deltaLength; + int32_t* hashPtr; // test sphere info.setSphere(radiusX); ++testCount; - DoubleHashKey key = info.getHash(); - uint32_t* hashPtr = hashes.find(key.getHash()); - if (hashPtr && *hashPtr == key.getHash2()) { - std::cout << testCount << " hash collision radiusX = " << radiusX - << " h1 = 0x" << std::hex << key.getHash() - << " h2 = 0x" << std::hex << key.getHash2() - << std::endl; + HashKey key = info.getHash(); + hashPtr = hashes.find(key); + if (hashPtr) { + std::cout << testCount << " hash collision sphere radius = " << radiusX + << " h = 0x" << std::hex << key.getHash() << " : 0x" << *hashPtr + << std::dec << std::endl; ++numCollisions; assert(false); } else { - hashes.insert(key.getHash(), key.getHash2()); + hashes.insert(key, key.getHash()); } - for (int k = 0; k < 32; ++k) { - if (masks[k] & key.getHash2()) { - ++bits[k]; + // track bit distribution counts to evaluate hash function randomness + for (int j = 0; j < NUM_HASH_BITS; ++j) { + if (masks[j] & key.getHash()) { + ++bits[j]; } } for (int y = 1; y < numSteps && testCount < maxTests; ++y) { float radiusY = (float)y * deltaLength; - /* TODO: reimplement Cylinder and Capsule shapes - // test cylinder and capsule - int types[] = { CYLINDER_SHAPE_PROXYTYPE, CAPSULE_SHAPE_PROXYTYPE }; - for (int i = 0; i < 2; ++i) { - switch(types[i]) { - case CYLINDER_SHAPE_PROXYTYPE: { - info.setCylinder(radiusX, radiusY); - break; - } - case CAPSULE_SHAPE_PROXYTYPE: { - info.setCapsuleY(radiusX, radiusY); - break; - } - } - - ++testCount; - key = info.getHash(); - hashPtr = hashes.find(key.getHash()); - if (hashPtr && *hashPtr == key.getHash2()) { - std::cout << testCount << " hash collision radiusX = " << radiusX << " radiusY = " << radiusY - << " h1 = 0x" << std::hex << key.getHash() - << " h2 = 0x" << std::hex << key.getHash2() - << std::endl; - ++numCollisions; - assert(false); - } else { - hashes.insert(key.getHash(), key.getHash2()); - } - for (int k = 0; k < 32; ++k) { - if (masks[k] & key.getHash2()) { - ++bits[k]; - } - } - } - */ - for (int z = 1; z < numSteps && testCount < maxTests; ++z) { float radiusZ = (float)z * deltaLength; // test box info.setBox(glm::vec3(radiusX, radiusY, radiusZ)); ++testCount; - DoubleHashKey key = info.getHash(); - hashPtr = hashes.find(key.getHash()); - if (hashPtr && *hashPtr == key.getHash2()) { - std::cout << testCount << " hash collision radiusX = " << radiusX - << " radiusY = " << radiusY << " radiusZ = " << radiusZ - << " h1 = 0x" << std::hex << key.getHash() - << " h2 = 0x" << std::hex << key.getHash2() - << std::endl; + HashKey key = info.getHash(); + hashPtr = hashes.find(key); + if (hashPtr) { + std::cout << testCount << " hash collision box dimensions = < " << radiusX + << ", " << radiusY << ", " << radiusZ << " >" + << " h = 0x" << std::hex << key.getHash() << " : 0x" << *hashPtr << " : 0x" << key.getHash64() + << std::dec << std::endl; ++numCollisions; assert(false); } else { - hashes.insert(key.getHash(), key.getHash2()); + hashes.insert(key, key.getHash()); } - for (int k = 0; k < 32; ++k) { - if (masks[k] & key.getHash2()) { + // track bit distribution counts to evaluate hash function randomness + for (int k = 0; k < NUM_HASH_BITS; ++k) { + if (masks[k] & key.getHash()) { ++bits[k]; } } @@ -135,7 +105,8 @@ void ShapeInfoTests::testHashFunctions() { std::cout << msec << " msec with " << numCollisions << " collisions out of " << testCount << " hashes" << std::endl; // print out distribution of bits - for (int i = 0; i < 32; ++i) { + // ideally the numbers in each bin will be about the same + for (int i = 0; i < NUM_HASH_BITS; ++i) { std::cout << "bit 0x" << std::hex << masks[i] << std::dec << " = " << bits[i] << std::endl; } QCOMPARE(numCollisions, 0); @@ -146,15 +117,14 @@ void ShapeInfoTests::testBoxShape() { ShapeInfo info; glm::vec3 halfExtents(1.23f, 4.56f, 7.89f); info.setBox(halfExtents); - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; - DoubleHashKey otherKey = otherInfo.getHash(); + HashKey otherKey = otherInfo.getHash(); QCOMPARE(key.getHash(), otherKey.getHash()); - QCOMPARE(key.getHash2(), otherKey.getHash2()); delete shape; } @@ -163,15 +133,14 @@ void ShapeInfoTests::testSphereShape() { ShapeInfo info; float radius = 1.23f; info.setSphere(radius); - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; - DoubleHashKey otherKey = otherInfo.getHash(); + HashKey otherKey = otherInfo.getHash(); QCOMPARE(key.getHash(), otherKey.getHash()); - QCOMPARE(key.getHash2(), otherKey.getHash2()); delete shape; } @@ -182,15 +151,14 @@ void ShapeInfoTests::testCylinderShape() { float radius = 1.23f; float height = 4.56f; info.setCylinder(radius, height); - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; - DoubleHashKey otherKey = otherInfo.getHash(); + HashKey otherKey = otherInfo.getHash(); QCOMPARE(key.getHash(), otherKey.getHash()); - QCOMPARE(key.getHash2(), otherKey.getHash2()); delete shape; */ @@ -202,15 +170,14 @@ void ShapeInfoTests::testCapsuleShape() { float radius = 1.23f; float height = 4.56f; info.setCapsule(radius, height); - DoubleHashKey key = info.getHash(); + HashKey key = info.getHash(); btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); QCOMPARE(shape != nullptr, true); ShapeInfo otherInfo = info; - DoubleHashKey otherKey = otherInfo.getHash(); + HashKey otherKey = otherInfo.getHash(); QCOMPARE(key.getHash(), otherKey.getHash()); - QCOMPARE(key.getHash2(), otherKey.getHash2()); delete shape; */ diff --git a/tests/physics/src/ShapeInfoTests.h b/tests/physics/src/ShapeInfoTests.h index fbd89a13a8..1f6054dd1a 100644 --- a/tests/physics/src/ShapeInfoTests.h +++ b/tests/physics/src/ShapeInfoTests.h @@ -18,10 +18,6 @@ //#include "BulletTestUtils.h" //#include "../QTestExtensions.h" -// Enable this to manually run testHashCollisions -// (NOT a regular unit test; takes ~17 secs to run on an i7) -#define MANUAL_TEST false - class ShapeInfoTests : public QObject { Q_OBJECT private slots: