diff --git a/.gitignore b/.gitignore index 19f448765b..da71a5e1c3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,15 @@ Makefile local.properties android/libraries +# VSCode +# List taken from Github Global Ignores master@435c4d92 +# https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + # Xcode *.xcodeproj *.xcworkspace diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d3296a168..2c10e714a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,9 +112,8 @@ if (BUILD_CLIENT OR BUILD_SERVER) add_subdirectory(plugins) endif() -if (BUILD_TOOLS) - add_subdirectory(tools) -endif() +# BUILD_TOOLS option will be handled inside the tools's CMakeLists.txt because 'scribe' tool is required for build anyway +add_subdirectory(tools) if (BUILD_TESTS) add_subdirectory(tests) diff --git a/assignment-client/src/audio/AudioMixerSlavePool.cpp b/assignment-client/src/audio/AudioMixerSlavePool.cpp index d2c19d97ba..e28c96e259 100644 --- a/assignment-client/src/audio/AudioMixerSlavePool.cpp +++ b/assignment-client/src/audio/AudioMixerSlavePool.cpp @@ -97,7 +97,11 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) { #else // fill the queue std::for_each(_begin, _end, [&](const SharedNodePointer& node) { +#if defined(__clang__) && defined(Q_OS_LINUX) + _queue.push(node); +#else _queue.emplace(node); +#endif }); { diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index 8afbc1cfe4..25b88686b7 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -97,7 +97,11 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) { #else // fill the queue std::for_each(_begin, _end, [&](const SharedNodePointer& node) { +#if defined(__clang__) && defined(Q_OS_LINUX) + _queue.push(node); +#else _queue.emplace(node); +#endif }); { diff --git a/cmake/externals/glew/CMakeLists.txt b/cmake/externals/glew/CMakeLists.txt index 28a599bfa6..6c3208981d 100644 --- a/cmake/externals/glew/CMakeLists.txt +++ b/cmake/externals/glew/CMakeLists.txt @@ -9,7 +9,7 @@ ExternalProject_Add( ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/glew_simple_1.13.0.zip URL_MD5 73f833649e904257b35bf4e84f8bdfb5 - CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 diff --git a/cmake/externals/nvtt/CMakeLists.txt b/cmake/externals/nvtt/CMakeLists.txt index fa9d7b5ea1..00722bd1e0 100644 --- a/cmake/externals/nvtt/CMakeLists.txt +++ b/cmake/externals/nvtt/CMakeLists.txt @@ -31,7 +31,7 @@ else () ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi.zip URL_MD5 5794b950f8b265a9a41b2839b3bf7ebb - CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= + CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_POSITION_INDEPENDENT_CODE=ON LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake index 6df41257f2..ed80e03c6b 100644 --- a/cmake/macros/MemoryDebugger.cmake +++ b/cmake/macros/MemoryDebugger.cmake @@ -14,9 +14,17 @@ endif () if (HIFI_MEMORY_DEBUGGING) if (UNIX) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer") - SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") - SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address") + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # for clang on Linux + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize-recover=address") + else () + # for gcc on Linux + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=undefined -fsanitize=address") + SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=undefined -fsanitize=address") + endif() endif (UNIX) endif () endmacro(SETUP_MEMORY_DEBUGGER) diff --git a/cmake/macros/SetupQt.cmake b/cmake/macros/SetupQt.cmake index ece8607b9b..bbdce06f37 100644 --- a/cmake/macros/SetupQt.cmake +++ b/cmake/macros/SetupQt.cmake @@ -44,7 +44,11 @@ endfunction() # Sets the QT_CMAKE_PREFIX_PATH and QT_DIR variables # Also enables CMAKE_AUTOMOC and CMAKE_AUTORCC macro(setup_qt) - set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}") + # if QT_CMAKE_PREFIX_PATH was not specified before hand, + # try to use the environment variable + if (NOT QT_CMAKE_PREFIX_PATH) + set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}") + endif() if (("QT_CMAKE_PREFIX_PATH" STREQUAL "") OR (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}")) calculate_default_qt_dir(QT_DIR) set(QT_CMAKE_PREFIX_PATH "${QT_DIR}/lib/cmake") @@ -81,4 +85,4 @@ macro(setup_qt) add_paths_to_fixup_libs("${QT_DIR}/bin") endif () -endmacro() \ No newline at end of file +endmacro() diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 305a6475f6..b16ad58431 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -262,7 +262,13 @@ target_link_libraries( ) if (UNIX) - target_link_libraries(${TARGET_NAME} pthread) + if (CMAKE_SYSTEM_NAME MATCHES "Linux") + # Linux + target_link_libraries(${TARGET_NAME} pthread atomic) + else () + # OSX + target_link_libraries(${TARGET_NAME} pthread) + endif () endif(UNIX) # assume we are using a Qt build without bearer management diff --git a/interface/resources/html/commerce/backup_instructions.html b/interface/resources/html/commerce/backup_instructions.html new file mode 100644 index 0000000000..560894e33d --- /dev/null +++ b/interface/resources/html/commerce/backup_instructions.html @@ -0,0 +1,609 @@ + + + + + +Backing Up Your Private Keys | High Fidelity + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +

Backing Up Your Private Keys

+
+ + +

What are private keys?

+

A private key is a secret piece of text that is used to prove ownership, unlock confidential information and sign transactions.

+

In High Fidelity, your private keys are used to securely access the contents of your Wallet and Purchases.

+ +
+

Where are my private keys stored?"

+

By default, your private keys are only stored on your hard drive in High Fidelity Interface's AppData directory.

+

Here is the file path of your hifikey - you can browse to it using your file explorer.

+
HIFIKEY_PATH_REPLACEME
+ +
+

How should I make a backup of my private keys?

+

You should backup your .hifikey file above by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. Restore your backup by replacing the file in Interface's AppData directory with your backed-up copy.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

What happens if I lose my passphrase?

+

Your passphrase is used to encrypt your private keys. If you lose your passphrase, you will no longer be able to decrypt your private key file nor have access to the contents of your Wallet or My Purchases.

+

In order to guarantee your privacy, nobody can help you recover your passphrase, including High Fidelity. + +

Please write it down and store it securely.

+

 

+
+ +

Want to learn more?

+

You can find out much more about the blockchain and about commerce in High Fidelity by visiting our Docs site:

+

Visit High Fidelity's Docs

+
+ +
+
+ +
+ + diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 000e310a66..bb23148ec6 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -88,7 +88,7 @@ FocusScope { return; } var oldRecommendedRect = recommendedRect; - var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedOverlayRect(); + var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect(); var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, newRecommendedRectJS.width, newRecommendedRectJS.height); @@ -271,7 +271,7 @@ FocusScope { var oldRecommendedRect = recommendedRect; var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; - var newRecommendedRect = Controller.getRecommendedOverlayRect(); + var newRecommendedRect = Controller.getRecommendedHUDRect(); var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; var windows = d.getTopLevelWindows(); for (var i = 0; i < windows.length; ++i) { @@ -410,7 +410,7 @@ FocusScope { return; } - var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedOverlayRect(); + var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect(); var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, newRecommendedRectJS.width, newRecommendedRectJS.height); @@ -442,7 +442,7 @@ FocusScope { var oldRecommendedRect = recommendedRect; var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; - var newRecommendedRect = Controller.getRecommendedOverlayRect(); + var newRecommendedRect = Controller.getRecommendedHUDRect(); var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); } @@ -459,7 +459,7 @@ FocusScope { return; } - var recommended = Controller.getRecommendedOverlayRect(); + var recommended = Controller.getRecommendedHUDRect(); var maxX = recommended.x + recommended.width; var maxY = recommended.y + recommended.height; var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y); diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index b1f80ac5e8..12c2ec1835 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -167,6 +167,9 @@ Rectangle { } RalewayRegular { anchors.verticalCenter: parent.verticalCenter; + width: margins.sizeText + margins.sizeLevel + anchors.left: parent.left + anchors.leftMargin: margins.sizeCheckBox size: 16; color: hifi.colors.lightGrayText; text: qsTr("CHOOSE INPUT DEVICE"); diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index 99f3648ec3..fdf579420d 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -29,12 +29,22 @@ RowLayout { function playSound() { // FIXME: MyAvatar is not properly exposed to QML; MyAvatar.qmlPosition is a stopgap // FIXME: Audio.playSystemSound should not require position - sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition); - isPlaying = true; - sample.finished.connect(function() { isPlaying = false; sample = null; }); + if (sample === null && !isPlaying) { + sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition); + isPlaying = true; + sample.finished.connect(reset); + } } function stopSound() { - sample && sample.stop(); + if (sample && isPlaying) { + sample.stop(); + } + } + + function reset() { + sample.finished.disconnect(reset); + isPlaying = false; + sample = null; } Component.onCompleted: createSampleSound(); diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 32f324aea9..8d94e284ed 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -79,6 +79,7 @@ Rectangle { failureErrorText.text = result.message; root.activeView = "checkoutFailure"; } else { + root.itemHref = result.data.download_url; root.activeView = "checkoutSuccess"; } } @@ -114,7 +115,7 @@ Rectangle { } onItemHrefChanged: { - itemIsJson = root.itemHref.indexOf('.json') !== -1; + itemIsJson = root.itemHref.endsWith('.json'); } onItemPriceChanged: { @@ -125,7 +126,7 @@ Rectangle { id: notSetUpTimer; interval: 200; onTriggered: { - sendToScript({method: 'checkout_walletNotSetUp'}); + sendToScript({method: 'checkout_walletNotSetUp', itemId: itemId}); } } @@ -574,8 +575,8 @@ Rectangle { anchors.right: parent.right; text: "Rez It" onClicked: { - if (urlHandler.canHandleUrl(itemHref)) { - urlHandler.handleUrl(itemHref); + if (urlHandler.canHandleUrl(root.itemHref)) { + urlHandler.handleUrl(root.itemHref); } rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start(); @@ -892,6 +893,10 @@ Rectangle { } else { root.activeView = "checkoutSuccess"; } + root.balanceReceived = false; + root.purchasesReceived = false; + commerce.inventory(); + commerce.balance(); } // diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 420b51ba15..cc316a70e9 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -39,7 +39,7 @@ Item { sendToParent({method: "needsLogIn"}); } else if (walletStatus === 3) { commerce.getSecurityImage(); - } else { + } else if (walletStatus > 3) { console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus); } } diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 19728daa82..fe1f049e5e 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -25,16 +25,67 @@ Rectangle { HifiConstants { id: hifi; } id: root; - property string marketplaceId: ""; + property string marketplaceUrl; + property string certificateId; property string itemName: "--"; property string itemOwner: "--"; property string itemEdition: "--"; - property string dateOfPurchase: ""; + property string dateOfPurchase: "--"; property bool isLightbox: false; + property bool isMyCert: false; // Style color: hifi.colors.faintGray; Hifi.QmlCommerce { id: commerce; + + onCertificateInfoResult: { + if (result.status !== 'success') { + console.log("Failed to get certificate info", result.message); + } 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.itemName = result.data.marketplace_item_name; + + if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") { + titleBarText.text = "Invalid Certificate"; + titleBarText.color = hifi.colors.redHighlight; + popText.text = ""; + if (result.data.invalid_reason) { + errorText.text = result.data.invalid_reason; + } + } else if (result.data.transfer_status[0] === "pending") { + titleBarText.text = "Certificate Pending"; + errorText.text = "The status of this item is still pending confirmation. If the purchase is not confirmed, " + + "this entity will be cleaned up by the domain."; + errorText.color = hifi.colors.baseGray; + } + } + } + } + + onCertificateIdChanged: { + if (certificateId !== "") { + commerce.certificateInfo(certificateId); + } + } + + onVisibleChanged: { + if (!visible) { + titleBarText.text = "Certificate"; + popText.text = "PROOF OF PURCHASE"; + root.certificateId = ""; + root.itemName = "--"; + root.itemOwner = "--"; + root.itemEdition = "--"; + root.dateOfPurchase = "--"; + root.marketplaceUrl = ""; + root.isMyCert = false; + errorText.text = ""; + } } // This object is always used in a popup. @@ -115,7 +166,7 @@ Rectangle { size: 28; // Anchors anchors.top: itemNameHeader.bottom; - anchors.topMargin: 4; + anchors.topMargin: 8; anchors.left: itemNameHeader.left; anchors.right: itemNameHeader.right; height: paintedHeight; @@ -126,7 +177,7 @@ Rectangle { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplaceId}); + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); } onEntered: itemName.color = hifi.colors.blueHighlight; onExited: itemName.color = hifi.colors.blueAccent; @@ -140,14 +191,20 @@ Rectangle { size: 16; // Anchors anchors.top: itemName.bottom; - anchors.topMargin: 20; + anchors.topMargin: 28; anchors.left: parent.left; anchors.leftMargin: 45; anchors.right: parent.right; anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.lightGray; + } + FontLoader { id: ralewayRegular; source: "../../../../fonts/Raleway-Regular.ttf"; } + TextMetrics { + id: textMetrics; + font.family: ralewayRegular.name + text: root.itemOwner; } RalewayRegular { id: ownedBy; @@ -156,14 +213,31 @@ Rectangle { size: 22; // Anchors anchors.top: ownedByHeader.bottom; - anchors.topMargin: 4; + anchors.topMargin: 8; anchors.left: ownedByHeader.left; - anchors.right: ownedByHeader.right; - height: paintedHeight; + height: textMetrics.height; + width: root.isMyCert ? textMetrics.width + 25 : ownedByHeader.width; // Style color: hifi.colors.darkGray; elide: Text.ElideRight; } + AnonymousProRegular { + id: isMyCertText; + visible: root.isMyCert; + text: "(Private)"; + size: 18; + // Anchors + anchors.top: ownedBy.top; + anchors.topMargin: 4; + anchors.bottom: ownedBy.bottom; + anchors.left: ownedBy.right; + anchors.leftMargin: 4; + anchors.right: ownedByHeader.right; + // Style + color: hifi.colors.lightGray; + elide: Text.ElideRight; + verticalAlignment: Text.AlignVCenter; + } RalewayRegular { id: editionHeader; @@ -172,23 +246,23 @@ Rectangle { size: 16; // Anchors anchors.top: ownedBy.bottom; - anchors.topMargin: 20; + anchors.topMargin: 28; anchors.left: parent.left; anchors.leftMargin: 45; anchors.right: parent.right; anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.lightGray; } AnonymousProRegular { id: edition; text: root.itemEdition; // Text size - size: 22; + size: 18; // Anchors anchors.top: editionHeader.bottom; - anchors.topMargin: 4; + anchors.topMargin: 8; anchors.left: editionHeader.left; anchors.right: editionHeader.right; height: paintedHeight; @@ -199,31 +273,29 @@ Rectangle { RalewayRegular { id: dateOfPurchaseHeader; text: "DATE OF PURCHASE"; - visible: root.dateOfPurchase !== ""; // Text size size: 16; // Anchors - anchors.top: ownedBy.bottom; - anchors.topMargin: 20; + anchors.top: edition.bottom; + anchors.topMargin: 28; anchors.left: parent.left; anchors.leftMargin: 45; anchors.right: parent.right; anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.lightGray; } AnonymousProRegular { id: dateOfPurchase; text: root.dateOfPurchase; - visible: root.dateOfPurchase !== ""; // Text size - size: 22; + size: 18; // Anchors - anchors.top: editionHeader.bottom; - anchors.topMargin: 4; - anchors.left: editionHeader.left; - anchors.right: editionHeader.right; + anchors.top: dateOfPurchaseHeader.bottom; + anchors.topMargin: 8; + anchors.left: dateOfPurchaseHeader.left; + anchors.right: dateOfPurchaseHeader.right; height: paintedHeight; // Style color: hifi.colors.darkGray; @@ -231,15 +303,13 @@ Rectangle { RalewayRegular { id: errorText; - text: "Here we will display some text if there's an error with the certificate " + - "(DMCA takedown, invalid cert, location of item updated)"; // Text size size: 20; // Anchors - anchors.top: root.dateOfPurchase !== "" ? dateOfPurchase.bottom : edition.bottom; - anchors.topMargin: 40; - anchors.left: root.dateOfPurchase !== "" ? dateOfPurchase.left : edition.left; - anchors.right: root.dateOfPurchase !== "" ? dateOfPurchase.right : edition.right; + anchors.top: dateOfPurchase.bottom; + anchors.topMargin: 36; + anchors.left: dateOfPurchase.left; + anchors.right: dateOfPurchase.right; anchors.bottom: parent.bottom; // Style wrapMode: Text.WordWrap; @@ -254,7 +324,7 @@ Rectangle { Item { id: buttonsContainer; anchors.bottom: parent.bottom; - anchors.bottomMargin: 50; + anchors.bottomMargin: 30; anchors.left: parent.left; anchors.right: parent.right; height: 50; @@ -281,6 +351,7 @@ Rectangle { // "Show In Marketplace" button HifiControlsUit.Button { id: showInMarketplaceButton; + enabled: root.marketplaceUrl; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.top: parent.top; @@ -290,7 +361,7 @@ Rectangle { height: 50; text: "View In Market" onClicked: { - sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', itemId: root.marketplaceId}); + sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); } } } @@ -313,19 +384,42 @@ Rectangle { // function fromScript(message) { switch (message.method) { - case 'inspectionCertificate_setMarketplaceId': - root.marketplaceId = message.marketplaceId; - break; - case 'inspectionCertificate_setItemInfo': - root.itemName = message.itemName; - root.itemOwner = message.itemOwner; - root.itemEdition = message.itemEdition; + case 'inspectionCertificate_setCertificateId': + root.certificateId = message.certificateId; break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); } } signal sendToScript(var message); + + function getFormattedDate(timestamp) { + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; + } + + var a = new Date(timestamp); + var year = a.getFullYear(); + var month = addLeadingZero(a.getMonth()); + var day = addLeadingZero(a.getDate()); + var hour = a.getHours(); + var drawnHour = hour; + if (hour === 0) { + drawnHour = 12; + } else if (hour > 12) { + drawnHour -= 12; + } + drawnHour = addLeadingZero(drawnHour); + + var amOrPm = "AM"; + if (hour >= 12) { + amOrPm = "PM"; + } + + var min = addLeadingZero(a.getMinutes()); + var sec = addLeadingZero(a.getSeconds()); + return year + '-' + month + '-' + day + '
' + drawnHour + ':' + min + amOrPm; + } // // FUNCTION DEFINITIONS END // diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index a026a818c0..e7e16668fe 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -34,8 +34,11 @@ Item { property string itemId; property string itemPreviewImageUrl; property string itemHref; + property string certificateId; property int displayedItemCount; property int itemEdition; + property int numberSold; + property int limitedRun; property string originalStatusText; property string originalStatusColor; @@ -50,7 +53,6 @@ Item { statusText.text = "CONFIRMED!"; statusText.color = hifi.colors.blueAccent; confirmedTimer.start(); - root.purchaseStatusChanged = false; } } @@ -60,6 +62,7 @@ Item { onTriggered: { statusText.text = root.originalStatusText; statusText.color = root.originalStatusColor; + root.purchaseStatusChanged = false; } } @@ -166,7 +169,7 @@ Item { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToPurchases({method: 'purchases_itemCertificateClicked', itemMarketplaceId: root.itemId}); + sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId}); } onEntered: { certificateIcon.color = hifi.colors.black; @@ -203,7 +206,7 @@ Item { Item { id: statusContainer; - visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated"; + visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged; anchors.left: itemName.left; anchors.top: certificateContainer.bottom; anchors.topMargin: 8; @@ -222,6 +225,8 @@ Item { "PENDING..." } else if (root.purchaseStatus === "invalidated") { "INVALIDATED" + } else if (root.numberSold !== -1) { + ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) } else { "" } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 0bb1515b69..b5697f687d 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -78,11 +78,17 @@ Rectangle { onInventoryResult: { purchasesReceived = true; + if (root.pendingInventoryReply) { + inventoryTimer.start(); + } + if (result.status !== 'success') { console.log("Failed to get purchases", result.message); } else { + var inventoryResult = processInventoryResult(result.data.assets); + purchasesModel.clear(); - purchasesModel.append(result.data.assets); + purchasesModel.append(inventoryResult); if (previousPurchasesModel.count !== 0) { checkIfAnyItemStatusChanged(); @@ -93,13 +99,9 @@ Rectangle { purchasesModel.setProperty(i, "statusChanged", false); } } - previousPurchasesModel.append(result.data.assets); + previousPurchasesModel.append(inventoryResult); buildFilteredPurchasesModel(); - - if (root.pendingInventoryReply) { - inventoryTimer.start(); - } } root.pendingInventoryReply = false; @@ -110,7 +112,7 @@ Rectangle { id: notSetUpTimer; interval: 200; onTriggered: { - sendToScript({method: 'checkout_walletNotSetUp'}); + sendToScript({method: 'purchases_walletNotSetUp'}); } } @@ -424,10 +426,13 @@ Rectangle { itemName: title; itemId: id; itemPreviewImageUrl: preview; - itemHref: root_file_url; + itemHref: download_url; + certificateId: certificate_id; purchaseStatus: status; purchaseStatusChanged: statusChanged; itemEdition: model.edition_number; + numberSold: model.number_sold; + limitedRun: model.limited_run; displayedItemCount: model.displayedItemCount; anchors.topMargin: 12; anchors.bottomMargin: 12; @@ -590,6 +595,17 @@ Rectangle { // FUNCTION DEFINITIONS START // + function processInventoryResult(inventory) { + for (var i = 0; i < inventory.length; i++) { + if (inventory[i].status.length > 1) { + console.log("WARNING: Inventory result index " + i + " has a status of length >1!") + } + inventory[i].status = inventory[i].status[0]; + inventory[i].categories = inventory[i].categories.join(';'); + } + return inventory; + } + function populateDisplayedItemCounts() { var itemCountDictionary = {}; var currentItemId; @@ -669,8 +685,7 @@ Rectangle { titleBarContainer.referrerURL = message.referrerURL; filterBar.text = message.filterText ? message.filterText : ""; break; - case 'inspectionCertificate_setMarketplaceId': - case 'inspectionCertificate_setItemInfo': + case 'inspectionCertificate_setCertificateId': inspectionCertificate.fromScript(message); break; case 'purchases_showMyItems': diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 21548ea788..65c06994f8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -30,12 +30,14 @@ Item { id: commerce; onKeyFilePathIfExistsResult: { - keyFilePath = path; + root.keyFilePath = path; } } - Component.onCompleted: { - commerce.getKeyFilePathIfExists(); + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePathIfExists(); + } } RalewaySemiBold { @@ -103,7 +105,7 @@ Item { ListElement { isExpanded: false; question: "What is a 'Security Pic'?" - answer: qsTr("Your Security Pic is an encrypted image that you selected during Wallet Setup. It acts as an extra layer of Wallet security.

When you see your Security Pic, you know that your actions and data are securely making use of your private keys.

If you don't see your Security Pic on a page that is asking you for your Wallet passphrase, someone untrustworthy may be trying to gain access to your Wallet.

The Pic is stored on your hard drive inside the same file as your private keys."); + answer: qsTr("Your Security Pic is an encrypted image that you selected during Wallet Setup. It acts as an extra layer of Wallet security.

When you see your Security Pic, you know that your actions and data are securely making use of your private keys.

If you don't see your Security Pic on a page that is asking you for your Wallet passphrase, someone untrustworthy may be trying to gain access to your Wallet.

The encrypted Pic is stored on your hard drive inside the same file as your private keys."); } ListElement { isExpanded: false; diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index 5bd88ba790..8d5d9f97de 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -197,6 +197,8 @@ Item { height: 50; echoMode: TextInput.Password; placeholderText: "passphrase"; + activeFocusOnPress: true; + activeFocusOnTab: true; onFocusChanged: { root.keyboardRaised = focus; @@ -206,8 +208,8 @@ Item { anchors.fill: parent; onClicked: { - parent.focus = true; root.keyboardRaised = true; + mouse.accepted = false; } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 9b70bb1f71..0f2edbe913 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -25,13 +25,13 @@ Item { HifiConstants { id: hifi; } id: root; - property string keyFilePath: ""; + property string keyFilePath; Hifi.QmlCommerce { id: commerce; onKeyFilePathIfExistsResult: { - keyFilePath = path; + root.keyFilePath = path; } } @@ -232,6 +232,12 @@ Item { anchors.rightMargin: 55; anchors.bottom: parent.bottom; + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePathIfExists(); + } + } + HiFiGlyphs { id: yourPrivateKeysImage; text: hifi.glyphs.walletKey; @@ -320,8 +326,9 @@ Item { height: 40; onClicked: { - Qt.openUrlExternally("https://www.highfidelity.com/"); - Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'))); + var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); + Qt.openUrlExternally(keyPath + "/backup_instructions.html"); + Qt.openUrlExternally(keyPath); removeHmdContainer.visible = true; removeHmdContainerTimer.start(); } diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 9056d5bed3..9beadd3361 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -176,6 +176,8 @@ Rectangle { commerce.getWalletStatus(); } else if (msg.referrer === 'purchases') { sendToScript({method: 'goToPurchases'}); + } else { + sendToScript({method: 'goToMarketplaceItemPage', itemId: msg.referrer}); } } else if (msg.method === 'walletSetup_raiseKeyboard') { root.keyboardRaised = true; @@ -283,7 +285,7 @@ Rectangle { Connections { onSendSignalToParent: { if (msg.method === "authSuccess") { - root.activeView = "walletHome"; + commerce.getWalletStatus(); } else { sendToScript(msg); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index b94616bd7a..50891deb60 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -299,10 +299,14 @@ Item { // function getFormattedDate(timestamp) { + function addLeadingZero(n) { + return n < 10 ? '0' + n : '' + n; + } + var a = new Date(timestamp); var year = a.getFullYear(); - var month = a.getMonth(); - var day = a.getDate(); + var month = addLeadingZero(a.getMonth()); + var day = addLeadingZero(a.getDate()); var hour = a.getHours(); var drawnHour = hour; if (hour === 0) { @@ -310,14 +314,15 @@ Item { } else if (hour > 12) { drawnHour -= 12; } + drawnHour = addLeadingZero(drawnHour); var amOrPm = "AM"; if (hour >= 12) { amOrPm = "PM"; } - var min = a.getMinutes(); - var sec = a.getSeconds(); + var min = addLeadingZero(a.getMinutes()); + var sec = addLeadingZero(a.getSeconds()); return year + '-' + month + '-' + day + '
' + drawnHour + ':' + min + amOrPm; } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index 898cdf0ef2..0075e86bdc 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -30,6 +30,7 @@ Item { property string lastPage; property bool hasShownSecurityImageTip: false; property string referrer; + property string keyFilePath; Image { anchors.fill: parent; @@ -58,7 +59,7 @@ Item { } onKeyFilePathIfExistsResult: { - keyFilePath.text = path; + root.keyFilePath = path; } } @@ -608,7 +609,7 @@ Item { anchors.fill: parent; RalewaySemiBold { - id: keyFilePathText; + id: keyFilePathHelperText; text: "Private Key File Location:"; size: 18; anchors.top: parent.top; @@ -627,7 +628,7 @@ Item { colorScheme: hifi.colorSchemes.dark; anchors.left: parent.left; anchors.leftMargin: 30; - anchors.top: keyFilePathText.bottom; + anchors.top: keyFilePathHelperText.bottom; anchors.topMargin: 8; height: 24; width: height; @@ -643,11 +644,12 @@ Item { } onClicked: { - Qt.openUrlExternally("file:///" + keyFilePath.text.substring(0, keyFilePath.text.lastIndexOf('/'))); + Qt.openUrlExternally("file:///" + keyFilePath.substring(0, keyFilePath.lastIndexOf('/'))); } } RalewayRegular { - id: keyFilePath; + id: keyFilePathText; + text: root.keyFilePath; size: 18; anchors.top: clipboardButton.top; anchors.left: clipboardButton.right; @@ -670,7 +672,7 @@ Item { id: openInstructionsButton; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.dark; - anchors.top: keyFilePath.bottom; + anchors.top: keyFilePathText.bottom; anchors.topMargin: 30; anchors.left: parent.left; anchors.leftMargin: 30; @@ -682,8 +684,9 @@ Item { instructions01Container.visible = false; instructions02Container.visible = true; keysReadyPageFinishButton.visible = true; - Qt.openUrlExternally("https://www.highfidelity.com/"); - Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'))); + var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); + Qt.openUrlExternally(keyPath + "/backup_instructions.html"); + Qt.openUrlExternally(keyPath); } } } diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 58091d9fab..98a73eb0fd 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -84,7 +84,7 @@ Item { } function urlHelper(src) { - if (src.match(/\bhttp/)) { + if (src.match(/\bhttp/) || src.match(/\bfile:/)) { return src; } else { return "../../../" + src; @@ -168,7 +168,7 @@ Item { PropertyChanges { target: text - color: "#ffffff" + color: captionColorOverride !== "" ? captionColorOverride: "#ffffff" text: tabletButton.hoverText } @@ -194,7 +194,7 @@ Item { PropertyChanges { target: text - color: "#333333" + color: captionColorOverride !== "" ? captionColorOverride: "#333333" text: tabletButton.activeText } @@ -225,7 +225,7 @@ Item { PropertyChanges { target: text - color: "#333333" + color: captionColorOverride !== "" ? captionColorOverride: "#333333" text: tabletButton.activeHoverText } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index bbf2d019fb..63149ad23b 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -34,7 +34,7 @@ StateImage { } function urlHelper(src) { - if (src.match(/\bhttp/)) { + if (src.match(/\bhttp/) || src.match(/\bfile:/)) { return src; } else { return "../../../" + src; diff --git a/interface/resources/qml/windows/TabletModalWindow.qml b/interface/resources/qml/windows/TabletModalWindow.qml index 05f192f7a7..e21cb6b224 100644 --- a/interface/resources/qml/windows/TabletModalWindow.qml +++ b/interface/resources/qml/windows/TabletModalWindow.qml @@ -15,7 +15,7 @@ import "." Rectangle { id: modalWindow layer.enabled: true - property var title: "Modal" + property var title: "Open" width: tabletRoot.width height: tabletRoot.height color: "#80000000" diff --git a/interface/resources/shaders/hmd_ui.frag b/interface/resources/shaders/hmd_ui.frag deleted file mode 100644 index 5341ab575d..0000000000 --- a/interface/resources/shaders/hmd_ui.frag +++ /dev/null @@ -1,30 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/07/11 -// Copyright 2013-2016 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 -// - -uniform sampler2D sampler; - -struct OverlayData { - mat4 mvp; - float alpha; -}; - -layout(std140) uniform overlayBuffer { - OverlayData overlay; -}; - -in vec2 vTexCoord; - -out vec4 FragColor; - -void main() { - FragColor = texture(sampler, vTexCoord); - FragColor.a *= overlay.alpha; - if (FragColor.a <= 0.0) { - discard; - } -} \ No newline at end of file diff --git a/interface/resources/shaders/hmd_ui.vert b/interface/resources/shaders/hmd_ui.vert deleted file mode 100644 index 41b9b3666f..0000000000 --- a/interface/resources/shaders/hmd_ui.vert +++ /dev/null @@ -1,28 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/07/11 -// Copyright 2013-2016 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 -// - -struct OverlayData { - mat4 mvp; - float alpha; -}; - -layout(std140) uniform overlayBuffer { - OverlayData overlay; -}; - -mat4 mvp = overlay.mvp; - -layout(location = 0) in vec3 Position; -layout(location = 3) in vec2 TexCoord; - -out vec2 vTexCoord; - -void main() { - gl_Position = mvp * vec4(Position, 1); - vTexCoord = TexCoord; -} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 69dc0cdf03..7e3e2440f5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -797,8 +797,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif - _logger = new FileLogger(this); // After setting organization name in order to get correct directory - + + _logger = new FileLogger(this); qInstallMessageHandler(messageHandler); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); @@ -814,6 +814,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo (new DeadlockWatchdogThread())->start(); } + // Set File Logger Session UUID + auto avatarManager = DependencyManager::get(); + auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr; + auto accountManager = DependencyManager::get(); + + _logger->setSessionID(accountManager->getSessionID()); + if (steamClient) { qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID(); } @@ -930,8 +937,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // send a location update immediately discoverabilityManager->updateLocation(); - auto myAvatar = getMyAvatar(); - connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated); @@ -942,9 +947,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // you might think we could just do this in NodeList but we only want this connection for Interface connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); - // connect to appropriate slots on AccountManager - auto accountManager = DependencyManager::get(); - auto dialogsManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); @@ -1636,12 +1638,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; QJsonObject bytesDownloaded; - bytesDownloaded["atp"] = statTracker->getStat(STAT_ATP_RESOURCE_TOTAL_BYTES).toInt(); - bytesDownloaded["http"] = statTracker->getStat(STAT_HTTP_RESOURCE_TOTAL_BYTES).toInt(); - bytesDownloaded["file"] = statTracker->getStat(STAT_FILE_RESOURCE_TOTAL_BYTES).toInt(); - bytesDownloaded["total"] = bytesDownloaded["atp"].toInt() + bytesDownloaded["http"].toInt() - + bytesDownloaded["file"].toInt(); - properties["bytesDownloaded"] = bytesDownloaded; + auto atpBytes = statTracker->getStat(STAT_ATP_RESOURCE_TOTAL_BYTES).toLongLong(); + auto httpBytes = statTracker->getStat(STAT_HTTP_RESOURCE_TOTAL_BYTES).toLongLong(); + auto fileBytes = statTracker->getStat(STAT_FILE_RESOURCE_TOTAL_BYTES).toLongLong(); + bytesDownloaded["atp"] = atpBytes; + bytesDownloaded["http"] = httpBytes; + bytesDownloaded["file"] = fileBytes; + bytesDownloaded["total"] = atpBytes + httpBytes + fileBytes; + properties["bytes_downloaded"] = bytesDownloaded; auto myAvatar = getMyAvatar(); glm::vec3 avatarPosition = myAvatar->getPosition(); @@ -2100,7 +2104,6 @@ Application::~Application() { _octreeProcessor.terminate(); _entityEditSender.terminate(); - DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); @@ -2688,15 +2691,10 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { bool Application::importJSONFromURL(const QString& urlString) { // we only load files that terminate in just .json (not .svo.json and not .ava.json) - // if they come from the High Fidelity Marketplace Assets CDN QUrl jsonURL { urlString }; - if (jsonURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) { - emit svoImportRequested(urlString); - return true; - } else { - return false; - } + emit svoImportRequested(urlString); + return true; } bool Application::importSVOFromURL(const QString& urlString) { @@ -5053,6 +5051,7 @@ void Application::update(float deltaTime) { float sensorToWorldScale = getMyAvatar()->getSensorToWorldScale(); appRenderArgs._sensorToWorldScale = sensorToWorldScale; + appRenderArgs._sensorToWorld = getMyAvatar()->getSensorToWorldMatrix(); { PROFILE_RANGE(render, "/buildFrustrumAndArgs"); { @@ -5094,6 +5093,7 @@ void Application::update(float deltaTime) { ipdScale *= sensorToWorldScale; auto baseProjection = appRenderArgs._renderArgs.getViewFrustum().getProjection(); + if (getActiveDisplayPlugin()->isStereo()) { // Stereo modes will typically have a larger projection matrix overall, // so we ask for the 'mono' projection matrix, which for stereo and HMD @@ -5638,14 +5638,37 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { return false; } + // We don't want to use EntityTree::findEntities(AABox, ...) method because that scan will snarf parented entities + // whose bounding boxes cannot be computed (it is too loose for our purposes here). Instead we manufacture + // custom filters and use the general-purpose EntityTree::findEntities(filter, ...) QVector entities; + AABox avatarBox(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); + // create two functions that use avatarBox (entityScan and elementScan), the second calls the first + std::function entityScan = [=](EntityItemPointer& entity) { + if (entity->shouldBePhysical()) { + bool success = false; + AABox entityBox = entity->getAABox(success); + // important: bail for entities that cannot supply a valid AABox + return success && avatarBox.touches(entityBox); + } + return false; + }; + std::function elementScan = [&](const OctreeElementPointer& element, void* unused) { + if (element->getAACube().touches(avatarBox)) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + entityTreeElement->getEntities(entityScan, entities); + return true; + } + return false; + }; + entityTree->withReadLock([&] { - AABox box(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); - entityTree->findEntities(box, entities); + // Pass the second function to the general-purpose EntityTree::findEntities() + // which will traverse the tree, apply the two filter functions (to element, then to entities) + // as it traverses. The end result will be a list of entities that match. + entityTree->findEntities(elementScan, entities); }); - // For reasons I haven't found, we don't necessarily have the full scene when we receive a stats packet. Apply - // a heuristic to try to decide when we actually know about all of the nearby entities. uint32_t nearbyCount = entities.size(); if (nearbyCount == _nearbyEntitiesCountAtLastPhysicsCheck) { _nearbyEntitiesStabilityCount++; @@ -7001,11 +7024,11 @@ glm::uvec2 Application::getUiSize() const { return result; } -QRect Application::getRecommendedOverlayRect() const { +QRect Application::getRecommendedHUDRect() const { auto uiSize = getUiSize(); QRect result(0, 0, uiSize.x, uiSize.y); if (_displayPlugin) { - result = getActiveDisplayPlugin()->getRecommendedOverlayRect(); + result = getActiveDisplayPlugin()->getRecommendedHUDRect(); } return result; } diff --git a/interface/src/Application.h b/interface/src/Application.h index 478642e2da..772646f379 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -160,7 +160,7 @@ public: QRect getRenderingGeometry() const; glm::uvec2 getUiSize() const; - QRect getRecommendedOverlayRect() const; + QRect getRecommendedHUDRect() const; QSize getDeviceSize() const; bool hasFocus() const; diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp index bbcd8266d7..541197a660 100644 --- a/interface/src/Application_render.cpp +++ b/interface/src/Application_render.cpp @@ -126,28 +126,18 @@ void Application::paintGL() { renderArgs._context->setStereoViews(stereoEyeOffsets); } + renderArgs._hudOperator = displayPlugin->getHUDOperator(); + renderArgs._hudTexture = _applicationOverlay.getOverlayTexture(); renderArgs._blitFramebuffer = finalFramebuffer; runRenderFrame(&renderArgs); } - gpu::Batch postCompositeBatch; - { - PROFILE_RANGE(render, "/postComposite"); - PerformanceTimer perfTimer("postComposite"); - renderArgs._batch = &postCompositeBatch; - renderArgs._batch->setViewportTransform(ivec4(0, 0, finalFramebufferSize.width(), finalFramebufferSize.height())); - renderArgs._batch->setViewTransform(renderArgs.getViewFrustum().getView()); - _overlays.render3DHUDOverlays(&renderArgs); - } - auto frame = _gpuContext->endFrame(); frame->frameIndex = _renderFrameCount; frame->framebuffer = finalFramebuffer; frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer) { DependencyManager::get()->releaseFramebuffer(framebuffer); }; - frame->overlay = _applicationOverlay.getOverlayTexture(); - frame->postCompositeBatch = postCompositeBatch; // deliver final scene rendering commands to the display plugin { PROFILE_RANGE(render, "/pluginOutput"); diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp index 8de40865b7..45ac80b054 100644 --- a/interface/src/assets/ATPAssetMigrator.cpp +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -78,7 +78,7 @@ void ATPAssetMigrator::loadEntityServerFile() { request->send(); } else { ++_errorCount; - qWarning() << "Count not create request for asset at" << migrationURL.toString(); + qWarning() << "Could not create request for asset at" << migrationURL.toString(); } }; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index a68a6fe929..80e599fb24 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -13,7 +13,6 @@ #include #include #include -#include "AccountManager.h" #include "Wallet.h" #include "Ledger.h" #include "CommerceLogging.h" @@ -48,13 +47,13 @@ Handler(receiveAt) Handler(balance) Handler(inventory) -void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request) { +void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); const QString URL = "/api/v1/commerce/"; JSONCallbackParameters callbackParams(this, success, this, fail); qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact); accountManager->sendRequest(URL + endpoint, - AccountManagerAuth::Required, + authType, method, callbackParams, QJsonDocument(request).toJson()); @@ -70,14 +69,14 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con } else { request["signature"] = QString("controlled failure!"); } - send(endpoint, success, fail, QNetworkAccessManager::PutOperation, request); + send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { auto wallet = DependencyManager::get(); QJsonObject request; request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); - send(endpoint, success, fail, QNetworkAccessManager::PostOperation, request); + send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, request); } void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) { @@ -192,7 +191,7 @@ void Ledger::history(const QStringList& keys) { void Ledger::resetSuccess(QNetworkReply& reply) { apiResponse("reset", reply); } void Ledger::resetFailure(QNetworkReply& reply) { failResponse("reset", reply); } void Ledger::reset() { - send("reset_user_hfc_account", "resetSuccess", "resetFailure", QNetworkAccessManager::PutOperation, QJsonObject()); + send("reset_user_hfc_account", "resetSuccess", "resetFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, QJsonObject()); } void Ledger::accountSuccess(QNetworkReply& reply) { @@ -217,7 +216,7 @@ void Ledger::accountFailure(QNetworkReply& reply) { failResponse("account", reply); } void Ledger::account() { - send("hfc_account", "accountSuccess", "accountFailure", QNetworkAccessManager::PutOperation, QJsonObject()); + send("hfc_account", "accountSuccess", "accountFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, QJsonObject()); } // The api/failResponse is called just for the side effect of logging. @@ -234,3 +233,28 @@ void Ledger::updateLocation(const QString& asset_id, const QString location, con auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); signedSend("transaction", transactionString, key, "location", "updateLocationSuccess", "updateLocationFailure", controlledFailure); } + +void Ledger::certificateInfoSuccess(QNetworkReply& reply) { + auto wallet = DependencyManager::get(); + auto accountManager = DependencyManager::get(); + + QByteArray response = reply.readAll(); + QJsonObject replyObject = QJsonDocument::fromJson(response).object(); + + QStringList keys = wallet->listPublicKeys(); + if (keys.count() != 0) { + QJsonObject data = replyObject["data"].toObject(); + if (data["transfer_recipient_key"].toString() == keys[0]) { + replyObject.insert("isMyCert", true); + } + } + qInfo(commerce) << "certificateInfo" << "response" << QJsonDocument(replyObject).toJson(QJsonDocument::Compact); + emit certificateInfoResult(replyObject); +} +void Ledger::certificateInfoFailure(QNetworkReply& reply) { failResponse("certificateInfo", reply); } +void Ledger::certificateInfo(const QString& certificateId) { + QString endpoint = "proof_of_purchase_status/transfer"; + QJsonObject request; + request["certificate_id"] = certificateId; + send(endpoint, "certificateInfoSuccess", "certificateInfoFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::None, request); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index da6c67224f..ae001010f0 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -17,6 +17,7 @@ #include #include #include +#include "AccountManager.h" class Ledger : public QObject, public Dependency { @@ -32,6 +33,7 @@ public: void account(); void reset(); void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); + void certificateInfo(const QString& certificateId); signals: void buyResult(QJsonObject result); @@ -41,6 +43,7 @@ signals: void historyResult(QJsonObject result); void accountResult(QJsonObject result); void locationUpdateResult(QJsonObject result); + void certificateInfoResult(QJsonObject result); public slots: void buySuccess(QNetworkReply& reply); @@ -59,11 +62,13 @@ public slots: void accountFailure(QNetworkReply& reply); void updateLocationSuccess(QNetworkReply& reply); void updateLocationFailure(QNetworkReply& reply); + void certificateInfoSuccess(QNetworkReply& reply); + void certificateInfoFailure(QNetworkReply& reply); private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); QJsonObject failResponse(const QString& label, QNetworkReply& reply); - void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request); + void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); void keysQuery(const QString& endpoint, const QString& success, const QString& fail); void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false); }; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 9f8847e8c7..803264fa9f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -15,7 +15,6 @@ #include "Ledger.h" #include "Wallet.h" #include -#include "scripting/WalletScriptingInterface.h" HIFI_QML_DEF(QmlCommerce) @@ -29,37 +28,13 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(ledger.data(), &Ledger::historyResult, this, &QmlCommerce::historyResult); connect(wallet.data(), &Wallet::keyFilePathIfExistsResult, this, &QmlCommerce::keyFilePathIfExistsResult); connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult); - connect(ledger.data(), &Ledger::accountResult, this, [&]() { - auto wallet = DependencyManager::get(); - auto walletScriptingInterface = DependencyManager::get(); - uint status; - - if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) { - status = (uint)WalletStatus::WALLET_STATUS_NOT_SET_UP; - } else if (!wallet->walletIsAuthenticatedWithPassphrase()) { - status = (uint)WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED; - } else { - status = (uint)WalletStatus::WALLET_STATUS_READY; - } - - walletScriptingInterface->setWalletStatus(status); - emit walletStatusResult(status); - }); + connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); + connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult); } void QmlCommerce::getWalletStatus() { - auto walletScriptingInterface = DependencyManager::get(); - uint status; - - if (DependencyManager::get()->isLoggedIn()) { - // This will set account info for the wallet, allowing us to decrypt and display the security image. - account(); - } else { - status = (uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN; - emit walletStatusResult(status); - walletScriptingInterface->setWalletStatus(status); - return; - } + auto wallet = DependencyManager::get(); + wallet->getWalletStatus(); } void QmlCommerce::getLoginStatus() { @@ -151,3 +126,8 @@ void QmlCommerce::account() { auto ledger = DependencyManager::get(); ledger->account(); } + +void QmlCommerce::certificateInfo(const QString& certificateId) { + auto ledger = DependencyManager::get(); + ledger->certificateInfo(certificateId); +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 8e6af6da65..ae63133425 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -27,13 +27,6 @@ class QmlCommerce : public OffscreenQmlDialog { public: QmlCommerce(QQuickItem* parent = nullptr); - enum WalletStatus { - WALLET_STATUS_NOT_LOGGED_IN = 0, - WALLET_STATUS_NOT_SET_UP, - WALLET_STATUS_NOT_AUTHENTICATED, - WALLET_STATUS_READY - }; - signals: void walletStatusResult(uint walletStatus); @@ -50,6 +43,7 @@ signals: void inventoryResult(QJsonObject result); void historyResult(QJsonObject result); void accountResult(QJsonObject result); + void certificateInfoResult(QJsonObject result); protected: Q_INVOKABLE void getWalletStatus(); @@ -70,6 +64,8 @@ protected: Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void reset(); Q_INVOKABLE void account(); + + Q_INVOKABLE void certificateInfo(const QString& certificateId); }; #endif // hifi_QmlCommerce_h diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 079e3a9479..d7227a58f7 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -41,6 +41,7 @@ #endif static const char* KEY_FILE = "hifikey"; +static const char* INSTRUCTIONS_FILE = "backup_instructions.html"; static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n"; static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n"; @@ -104,6 +105,38 @@ RSA* readKeys(const char* filename) { return key; } +bool writeBackupInstructions() { + QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html"); + QString filename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE); + QFile outputFile(filename); + bool retval = false; + + if (QFile::exists(filename)) + { + QFile::remove(filename); + } + QFile::copy(inputFilename, filename); + + if (QFile::exists(filename) && outputFile.open(QIODevice::ReadWrite)) { + + QByteArray fileData = outputFile.readAll(); + QString text(fileData); + + text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath()); + + outputFile.seek(0); // go to the beginning of the file + outputFile.write(text.toUtf8()); // write the new text back to the file + + outputFile.close(); // close the file handle. + + retval = true; + qCDebug(commerce) << "wrote html file successfully"; + } else { + qCDebug(commerce) << "failed to open output html file" << filename; + } + return retval; +} + bool writeKeys(const char* filename, RSA* keys) { FILE* fp; bool retval = false; @@ -121,6 +154,8 @@ bool writeKeys(const char* filename, RSA* keys) { QFile(QString(filename)).remove(); return retval; } + + writeBackupInstructions(); retval = true; qCDebug(commerce) << "wrote keys successfully"; @@ -282,9 +317,32 @@ void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArra Wallet::Wallet() { auto nodeList = DependencyManager::get(); + auto ledger = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); + + connect(ledger.data(), &Ledger::accountResult, this, [&]() { + auto wallet = DependencyManager::get(); + auto walletScriptingInterface = DependencyManager::get(); + uint status; + + if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) { + status = (uint)WalletStatus::WALLET_STATUS_NOT_SET_UP; + } else if (!wallet->walletIsAuthenticatedWithPassphrase()) { + status = (uint)WalletStatus::WALLET_STATUS_NOT_AUTHENTICATED; + } else { + status = (uint)WalletStatus::WALLET_STATUS_READY; + } + + walletScriptingInterface->setWalletStatus(status); + emit walletStatusResult(status); + }); + + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { + getWalletStatus(); + }); } Wallet::~Wallet() { @@ -470,7 +528,6 @@ bool Wallet::generateKeyPair() { // TODO: redo this soon -- need error checking and so on writeSecurityImage(_securityImage, keyFilePath()); - emit keyFilePathIfExistsResult(getKeyFilePath()); QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last(); QString key = keyPair.first->toBase64(); _publicKeys.push_back(key); @@ -628,6 +685,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) { QFile(QString(keyFilePath())).remove(); QFile(tempFileName).rename(QString(keyFilePath())); qCDebug(commerce) << "wallet written successfully"; + emit keyFilePathIfExistsResult(getKeyFilePath()); return true; } else { qCDebug(commerce) << "couldn't write security image to temp wallet"; @@ -682,3 +740,23 @@ bool Wallet::verifyOwnerChallenge(const QByteArray& encryptedText, const QString decryptedText = QString("hello"); return true; } + +void Wallet::account() { + auto ledger = DependencyManager::get(); + ledger->account(); +} + +void Wallet::getWalletStatus() { + auto walletScriptingInterface = DependencyManager::get(); + uint status; + + if (DependencyManager::get()->isLoggedIn()) { + // This will set account info for the wallet, allowing us to decrypt and display the security image. + account(); + } else { + status = (uint)WalletStatus::WALLET_STATUS_NOT_LOGGED_IN; + emit walletStatusResult(status); + walletScriptingInterface->setWalletStatus(status); + return; + } +} diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index acf9f8e45e..38c5299810 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -17,6 +17,7 @@ #include #include #include +#include "scripting/WalletScriptingInterface.h" #include @@ -50,10 +51,20 @@ public: void reset(); + void getWalletStatus(); + enum WalletStatus { + WALLET_STATUS_NOT_LOGGED_IN = 0, + WALLET_STATUS_NOT_SET_UP, + WALLET_STATUS_NOT_AUTHENTICATED, + WALLET_STATUS_READY + }; + signals: void securityImageResult(bool exists); void keyFilePathIfExistsResult(const QString& path); + void walletStatusResult(uint walletStatus); + private slots: void handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode); @@ -71,6 +82,8 @@ private: bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen); bool verifyOwnerChallenge(const QByteArray& encryptedText, const QString& publicKey, QString& decryptedText); + + void account(); }; #endif // hifi_Wallet_h diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 55ddd01123..0e0f13cd6c 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -49,11 +49,13 @@ LaserPointer::~LaserPointer() { } void LaserPointer::enable() { + QWriteLocker lock(getLock()); DependencyManager::get()->enableRayPick(_rayPickUID); _renderingEnabled = true; } void LaserPointer::disable() { + QWriteLocker lock(getLock()); DependencyManager::get()->disableRayPick(_rayPickUID); _renderingEnabled = false; if (!_currentRenderState.empty()) { @@ -67,6 +69,7 @@ void LaserPointer::disable() { } void LaserPointer::setRenderState(const std::string& state) { + QWriteLocker lock(getLock()); if (!_currentRenderState.empty() && state != _currentRenderState) { if (_renderStates.find(_currentRenderState) != _renderStates.end()) { disableRenderState(_renderStates[_currentRenderState]); @@ -79,6 +82,7 @@ void LaserPointer::setRenderState(const std::string& state) { } void LaserPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + QWriteLocker lock(getLock()); updateRenderStateOverlay(_renderStates[state].getStartID(), startProps); updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps); updateRenderStateOverlay(_renderStates[state].getEndID(), endProps); @@ -92,6 +96,11 @@ void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& } } +const RayPickResult LaserPointer::getPrevRayPickResult() { + QReadLocker lock(getLock()); + return DependencyManager::get()->getPrevRayPickResult(_rayPickUID); +} + void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState) { if (!renderState.getStartID().isNull()) { QVariantMap startProps; @@ -183,6 +192,8 @@ void LaserPointer::disableRenderState(const RenderState& renderState) { } void LaserPointer::update() { + // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts + QReadLocker lock(getLock()); RayPickResult prevRayPickResult = DependencyManager::get()->getPrevRayPickResult(_rayPickUID); if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && (prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) { @@ -198,6 +209,51 @@ void LaserPointer::update() { } } +void LaserPointer::setPrecisionPicking(const bool precisionPicking) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setPrecisionPicking(_rayPickUID, precisionPicking); +} + +void LaserPointer::setLaserLength(const float laserLength) { + QWriteLocker lock(getLock()); + _laserLength = laserLength; +} + +void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay) { + QWriteLocker lock(getLock()); + _objectLockEnd = std::pair(objectID, isOverlay); +} + +void LaserPointer::setIgnoreEntities(const QScriptValue& ignoreEntities) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIgnoreEntities(_rayPickUID, ignoreEntities); +} + +void LaserPointer::setIncludeEntities(const QScriptValue& includeEntities) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIncludeEntities(_rayPickUID, includeEntities); +} + +void LaserPointer::setIgnoreOverlays(const QScriptValue& ignoreOverlays) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIgnoreOverlays(_rayPickUID, ignoreOverlays); +} + +void LaserPointer::setIncludeOverlays(const QScriptValue& includeOverlays) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIncludeOverlays(_rayPickUID, includeOverlays); +} + +void LaserPointer::setIgnoreAvatars(const QScriptValue& ignoreAvatars) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIgnoreAvatars(_rayPickUID, ignoreAvatars); +} + +void LaserPointer::setIncludeAvatars(const QScriptValue& includeAvatars) { + QWriteLocker lock(getLock()); + DependencyManager::get()->setIncludeAvatars(_rayPickUID, includeAvatars); +} + RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : _startID(startID), _pathID(pathID), _endID(endID) { diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 5467a8233e..01dfe01cfd 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -58,22 +58,24 @@ public: QUuid getRayUID() { return _rayPickUID; } void enable(); void disable(); - const RayPickResult getPrevRayPickResult() { return DependencyManager::get()->getPrevRayPickResult(_rayPickUID); } + const RayPickResult getPrevRayPickResult(); void setRenderState(const std::string& state); // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps); - void setPrecisionPicking(const bool precisionPicking) { DependencyManager::get()->setPrecisionPicking(_rayPickUID, precisionPicking); } - void setLaserLength(const float laserLength) { _laserLength = laserLength; } - void setIgnoreEntities(const QScriptValue& ignoreEntities) { DependencyManager::get()->setIgnoreEntities(_rayPickUID, ignoreEntities); } - void setIncludeEntities(const QScriptValue& includeEntities) { DependencyManager::get()->setIncludeEntities(_rayPickUID, includeEntities); } - void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { DependencyManager::get()->setIgnoreOverlays(_rayPickUID, ignoreOverlays); } - void setIncludeOverlays(const QScriptValue& includeOverlays) { DependencyManager::get()->setIncludeOverlays(_rayPickUID, includeOverlays); } - void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { DependencyManager::get()->setIgnoreAvatars(_rayPickUID, ignoreAvatars); } - void setIncludeAvatars(const QScriptValue& includeAvatars) { DependencyManager::get()->setIncludeAvatars(_rayPickUID, includeAvatars); } + void setPrecisionPicking(const bool precisionPicking); + void setLaserLength(const float laserLength); + void setLockEndUUID(QUuid objectID, const bool isOverlay); - void setLockEndUUID(QUuid objectID, const bool isOverlay) { _objectLockEnd = std::pair(objectID, isOverlay); } + void setIgnoreEntities(const QScriptValue& ignoreEntities); + void setIncludeEntities(const QScriptValue& includeEntities); + void setIgnoreOverlays(const QScriptValue& ignoreOverlays); + void setIncludeOverlays(const QScriptValue& includeOverlays); + void setIgnoreAvatars(const QScriptValue& ignoreAvatars); + void setIncludeAvatars(const QScriptValue& includeAvatars); + + QReadWriteLock* getLock() { return &_lock; } void update(); @@ -89,6 +91,7 @@ private: std::pair _objectLockEnd { std::pair(QUuid(), false)}; QUuid _rayPickUID; + QReadWriteLock _lock; void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState); diff --git a/interface/src/raypick/LaserPointerManager.cpp b/interface/src/raypick/LaserPointerManager.cpp index b19ecc14f0..8615a96c3f 100644 --- a/interface/src/raypick/LaserPointerManager.cpp +++ b/interface/src/raypick/LaserPointerManager.cpp @@ -17,7 +17,6 @@ QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const La QWriteLocker containsLock(&_containsLock); QUuid id = QUuid::createUuid(); _laserPointers[id] = laserPointer; - _laserPointerLocks[id] = std::make_shared(); return id; } return QUuid(); @@ -26,46 +25,45 @@ QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const La void LaserPointerManager::removeLaserPointer(const QUuid uid) { QWriteLocker lock(&_containsLock); _laserPointers.remove(uid); - _laserPointerLocks.remove(uid); } void LaserPointerManager::enableLaserPointer(const QUuid uid) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->enable(); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->enable(); } } void LaserPointerManager::disableLaserPointer(const QUuid uid) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->disable(); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->disable(); } } void LaserPointerManager::setRenderState(QUuid uid, const std::string& renderState) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setRenderState(renderState); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setRenderState(renderState); } } void LaserPointerManager::editRenderState(QUuid uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->editRenderState(state, startProps, pathProps, endProps); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->editRenderState(state, startProps, pathProps, endProps); } } const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid uid) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QReadLocker laserLock(_laserPointerLocks[uid].get()); - return _laserPointers[uid]->getPrevRayPickResult(); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + return laserPointer.value()->getPrevRayPickResult(); } return RayPickResult(); } @@ -73,80 +71,79 @@ const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid uid) { void LaserPointerManager::update() { QReadLocker lock(&_containsLock); for (QUuid& uid : _laserPointers.keys()) { - // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts - QReadLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->update(); + auto laserPointer = _laserPointers.find(uid); + laserPointer.value()->update(); } } void LaserPointerManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setPrecisionPicking(precisionPicking); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setPrecisionPicking(precisionPicking); } } void LaserPointerManager::setLaserLength(QUuid uid, const float laserLength) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setLaserLength(laserLength); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setLaserLength(laserLength); } } void LaserPointerManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIgnoreEntities(ignoreEntities); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setIgnoreEntities(ignoreEntities); } } void LaserPointerManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIncludeEntities(includeEntities); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setIncludeEntities(includeEntities); } } void LaserPointerManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIgnoreOverlays(ignoreOverlays); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setIgnoreOverlays(ignoreOverlays); } } void LaserPointerManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIncludeOverlays(includeOverlays); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setIncludeOverlays(includeOverlays); } } void LaserPointerManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIgnoreAvatars(ignoreAvatars); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setIgnoreAvatars(ignoreAvatars); } } void LaserPointerManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setIncludeAvatars(includeAvatars); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setIncludeAvatars(includeAvatars); } } void LaserPointerManager::setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay) { QReadLocker lock(&_containsLock); - if (_laserPointers.contains(uid)) { - QWriteLocker laserLock(_laserPointerLocks[uid].get()); - _laserPointers[uid]->setLockEndUUID(objectID, isOverlay); + auto laserPointer = _laserPointers.find(uid); + if (laserPointer != _laserPointers.end()) { + laserPointer.value()->setLockEndUUID(objectID, isOverlay); } } diff --git a/interface/src/raypick/LaserPointerManager.h b/interface/src/raypick/LaserPointerManager.h index 6494bb7056..b841877578 100644 --- a/interface/src/raypick/LaserPointerManager.h +++ b/interface/src/raypick/LaserPointerManager.h @@ -13,7 +13,6 @@ #include #include -#include #include "LaserPointer.h" @@ -46,7 +45,6 @@ public: private: QHash> _laserPointers; - QHash> _laserPointerLocks; QReadWriteLock _containsLock; }; diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 70170a8f85..a5b1299210 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -16,3 +16,47 @@ RayPick::RayPick(const RayPickFilter& filter, const float maxDistance, const boo _enabled(enabled) { } +void RayPick::enable() { + QWriteLocker lock(getLock()); + _enabled = true; +} + +void RayPick::disable() { + QWriteLocker lock(getLock()); + _enabled = false; +} + +const RayPickResult& RayPick::getPrevRayPickResult() { + QReadLocker lock(getLock()); + return _prevResult; +} + +void RayPick::setIgnoreEntities(const QScriptValue& ignoreEntities) { + QWriteLocker lock(getLock()); + _ignoreEntities = qVectorEntityItemIDFromScriptValue(ignoreEntities); +} + +void RayPick::setIncludeEntities(const QScriptValue& includeEntities) { + QWriteLocker lock(getLock()); + _includeEntities = qVectorEntityItemIDFromScriptValue(includeEntities); +} + +void RayPick::setIgnoreOverlays(const QScriptValue& ignoreOverlays) { + QWriteLocker lock(getLock()); + _ignoreOverlays = qVectorOverlayIDFromScriptValue(ignoreOverlays); +} + +void RayPick::setIncludeOverlays(const QScriptValue& includeOverlays) { + QWriteLocker lock(getLock()); + _includeOverlays = qVectorOverlayIDFromScriptValue(includeOverlays); +} + +void RayPick::setIgnoreAvatars(const QScriptValue& ignoreAvatars) { + QWriteLocker lock(getLock()); + _ignoreAvatars = qVectorEntityItemIDFromScriptValue(ignoreAvatars); +} + +void RayPick::setIncludeAvatars(const QScriptValue& includeAvatars) { + QWriteLocker lock(getLock()); + _includeAvatars = qVectorEntityItemIDFromScriptValue(includeAvatars); +} \ No newline at end of file diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 0686a05718..6dacc084b4 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -16,6 +16,7 @@ #include "EntityItemID.h" #include "ui/overlays/Overlay.h" +#include class RayPickFilter { public: @@ -102,13 +103,13 @@ public: virtual const PickRay getPickRay(bool& valid) const = 0; - void enable() { _enabled = true; } - void disable() { _enabled = false; } + void enable(); + void disable(); const RayPickFilter& getFilter() { return _filter; } float getMaxDistance() { return _maxDistance; } bool isEnabled() { return _enabled; } - const RayPickResult& getPrevRayPickResult() { return _prevResult; } + const RayPickResult& getPrevRayPickResult(); void setPrecisionPicking(bool precisionPicking) { _filter.setFlag(RayPickFilter::PICK_COURSE, !precisionPicking); } @@ -120,12 +121,14 @@ public: const QVector& getIncludeOverlays() { return _includeOverlays; } const QVector& getIgnoreAvatars() { return _ignoreAvatars; } const QVector& getIncludeAvatars() { return _includeAvatars; } - void setIgnoreEntities(const QScriptValue& ignoreEntities) { _ignoreEntities = qVectorEntityItemIDFromScriptValue(ignoreEntities); } - void setIncludeEntities(const QScriptValue& includeEntities) { _includeEntities = qVectorEntityItemIDFromScriptValue(includeEntities); } - void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { _ignoreOverlays = qVectorOverlayIDFromScriptValue(ignoreOverlays); } - void setIncludeOverlays(const QScriptValue& includeOverlays) { _includeOverlays = qVectorOverlayIDFromScriptValue(includeOverlays); } - void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { _ignoreAvatars = qVectorEntityItemIDFromScriptValue(ignoreAvatars); } - void setIncludeAvatars(const QScriptValue& includeAvatars) { _includeAvatars = qVectorEntityItemIDFromScriptValue(includeAvatars); } + void setIgnoreEntities(const QScriptValue& ignoreEntities); + void setIncludeEntities(const QScriptValue& includeEntities); + void setIgnoreOverlays(const QScriptValue& ignoreOverlays); + void setIncludeOverlays(const QScriptValue& includeOverlays); + void setIgnoreAvatars(const QScriptValue& ignoreAvatars); + void setIncludeAvatars(const QScriptValue& includeAvatars); + + QReadWriteLock* getLock() { return &_lock; } private: RayPickFilter _filter; @@ -139,6 +142,8 @@ private: QVector _includeOverlays; QVector _ignoreAvatars; QVector _includeAvatars; + + QReadWriteLock _lock; }; #endif // hifi_RayPick_h diff --git a/interface/src/raypick/RayPickManager.cpp b/interface/src/raypick/RayPickManager.cpp index bfc6e3fcb2..1728ecd01a 100644 --- a/interface/src/raypick/RayPickManager.cpp +++ b/interface/src/raypick/RayPickManager.cpp @@ -47,6 +47,7 @@ void RayPickManager::update() { RayPickCache results; for (auto& uid : _rayPicks.keys()) { std::shared_ptr rayPick = _rayPicks[uid]; + QWriteLocker lock(rayPick->getLock()); if (!rayPick->isEnabled() || rayPick->getFilter().doesPickNothing() || rayPick->getMaxDistance() < 0.0f) { continue; } @@ -114,7 +115,6 @@ void RayPickManager::update() { } } - QWriteLocker lock(_rayPickLocks[uid].get()); if (rayPick->getMaxDistance() == 0.0f || (rayPick->getMaxDistance() > 0.0f && res.distance < rayPick->getMaxDistance())) { rayPick->setRayPickResult(res); } else { @@ -127,7 +127,6 @@ QUuid RayPickManager::createRayPick(const std::string& jointName, const glm::vec QWriteLocker lock(&_containsLock); QUuid id = QUuid::createUuid(); _rayPicks[id] = std::make_shared(jointName, posOffset, dirOffset, filter, maxDistance, enabled); - _rayPickLocks[id] = std::make_shared(); return id; } @@ -135,7 +134,6 @@ QUuid RayPickManager::createRayPick(const RayPickFilter& filter, const float max QWriteLocker lock(&_containsLock); QUuid id = QUuid::createUuid(); _rayPicks[id] = std::make_shared(filter, maxDistance, enabled); - _rayPickLocks[id] = std::make_shared(); return id; } @@ -143,93 +141,91 @@ QUuid RayPickManager::createRayPick(const glm::vec3& position, const glm::vec3& QWriteLocker lock(&_containsLock); QUuid id = QUuid::createUuid(); _rayPicks[id] = std::make_shared(position, direction, filter, maxDistance, enabled); - _rayPickLocks[id] = std::make_shared(); return id; } void RayPickManager::removeRayPick(const QUuid uid) { QWriteLocker lock(&_containsLock); _rayPicks.remove(uid); - _rayPickLocks.remove(uid); } void RayPickManager::enableRayPick(const QUuid uid) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker rayPickLock(_rayPickLocks[uid].get()); - _rayPicks[uid]->enable(); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + rayPick.value()->enable(); } } void RayPickManager::disableRayPick(const QUuid uid) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker rayPickLock(_rayPickLocks[uid].get()); - _rayPicks[uid]->disable(); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + rayPick.value()->disable(); } } const RayPickResult RayPickManager::getPrevRayPickResult(const QUuid uid) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QReadLocker lock(_rayPickLocks[uid].get()); - return _rayPicks[uid]->getPrevRayPickResult(); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + return rayPick.value()->getPrevRayPickResult(); } return RayPickResult(); } void RayPickManager::setPrecisionPicking(QUuid uid, const bool precisionPicking) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setPrecisionPicking(precisionPicking); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + rayPick.value()->setPrecisionPicking(precisionPicking); } } void RayPickManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIgnoreEntities(ignoreEntities); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + rayPick.value()->setIgnoreEntities(ignoreEntities); } } void RayPickManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIncludeEntities(includeEntities); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + rayPick.value()->setIncludeEntities(includeEntities); } } void RayPickManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIgnoreOverlays(ignoreOverlays); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + rayPick.value()->setIgnoreOverlays(ignoreOverlays); } } void RayPickManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIncludeOverlays(includeOverlays); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + rayPick.value()->setIncludeOverlays(includeOverlays); } } void RayPickManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIgnoreAvatars(ignoreAvatars); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + rayPick.value()->setIgnoreAvatars(ignoreAvatars); } } void RayPickManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { QReadLocker containsLock(&_containsLock); - if (_rayPicks.contains(uid)) { - QWriteLocker lock(_rayPickLocks[uid].get()); - _rayPicks[uid]->setIncludeAvatars(includeAvatars); + auto rayPick = _rayPicks.find(uid); + if (rayPick != _rayPicks.end()) { + rayPick.value()->setIncludeAvatars(includeAvatars); } } \ No newline at end of file diff --git a/interface/src/raypick/RayPickManager.h b/interface/src/raypick/RayPickManager.h index 9717767f19..974022eb4d 100644 --- a/interface/src/raypick/RayPickManager.h +++ b/interface/src/raypick/RayPickManager.h @@ -15,7 +15,6 @@ #include #include -#include #include "RegisteredMetaTypes.h" @@ -47,7 +46,6 @@ public: private: QHash> _rayPicks; - QHash> _rayPickLocks; QReadWriteLock _containsLock; typedef QHash, std::unordered_map> RayPickCache; diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 5308be59bf..a1b2a9ccfc 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -21,6 +21,7 @@ #include #include #include +#include static const int AUTO_REFRESH_INTERVAL = 1000; @@ -104,6 +105,21 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, startedCallback.call(); + QFileInfo fileInfo { path }; + int64_t size { fileInfo.size() }; + + QString extension = ""; + auto idx = path.lastIndexOf("."); + if (idx >= 0) { + extension = path.mid(idx + 1); + } + + UserActivityLogger::getInstance().logAction("uploading_asset", { + { "size", (qint64)size }, + { "mapping", mapping }, + { "extension", extension} + }); + auto upload = DependencyManager::get()->createUpload(path); QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { if (upload->getError() != AssetUpload::NoError) { diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 9fbd01817a..5c55e2094b 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -91,8 +91,8 @@ glm::vec2 ControllerScriptingInterface::getViewportDimensions() const { return qApp->getUiSize(); } -QVariant ControllerScriptingInterface::getRecommendedOverlayRect() const { - auto rect = qApp->getRecommendedOverlayRect(); +QVariant ControllerScriptingInterface::getRecommendedHUDRect() const { + auto rect = qApp->getRecommendedHUDRect(); return qRectToVariant(rect); } diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 6d197477bb..8c825a17de 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -66,7 +66,7 @@ public slots: virtual void releaseEntityClickEvents(); virtual glm::vec2 getViewportDimensions() const; - virtual QVariant getRecommendedOverlayRect() const; + virtual QVariant getRecommendedHUDRect() const; signals: void keyPressEvent(const KeyEvent& event); diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index efab178798..34f196ac70 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -29,7 +29,7 @@ int DesktopScriptingInterface::getHeight() { return size.height(); } -void DesktopScriptingInterface::setOverlayAlpha(float alpha) { +void DesktopScriptingInterface::setHUDAlpha(float alpha) { qApp->getApplicationCompositor().setAlpha(alpha); } diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index 2825065e90..e62a3584d6 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -22,7 +22,7 @@ class DesktopScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus public: - Q_INVOKABLE void setOverlayAlpha(float alpha); + Q_INVOKABLE void setHUDAlpha(float alpha); Q_INVOKABLE void show(const QString& path, const QString& title); int getWidth(); diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 555e9477b0..99fdd5fbde 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -18,6 +18,11 @@ CheckoutProxy::CheckoutProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(q WalletScriptingInterface::WalletScriptingInterface() { } +void WalletScriptingInterface::refreshWalletStatus() { + auto wallet = DependencyManager::get(); + wallet->getWalletStatus(); +} + static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml"; void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) { if (QThread::currentThread() != thread()) { diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 31b42094cf..038c580197 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -20,6 +20,7 @@ #include #include #include "Application.h" +#include "commerce/Wallet.h" class CheckoutProxy : public QmlWrapper { Q_OBJECT @@ -36,6 +37,7 @@ class WalletScriptingInterface : public QObject, public Dependency { public: WalletScriptingInterface(); + Q_INVOKABLE void refreshWalletStatus(); Q_INVOKABLE uint getWalletStatus() { return _walletStatus; } void setWalletStatus(const uint& status) { _walletStatus = status; } @@ -43,6 +45,7 @@ public: signals: void walletStatusChanged(); + void walletNotSetup(); private: uint _walletStatus; diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 4210e76097..a34240483b 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -26,7 +26,8 @@ Base3DOverlay::Base3DOverlay() : _isSolid(DEFAULT_IS_SOLID), _isDashedLine(DEFAULT_IS_DASHED_LINE), _ignoreRayIntersection(false), - _drawInFront(false) + _drawInFront(false), + _drawHUDLayer(false) { } @@ -38,6 +39,7 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : _isDashedLine(base3DOverlay->_isDashedLine), _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), _drawInFront(base3DOverlay->_drawInFront), + _drawHUDLayer(base3DOverlay->_drawHUDLayer), _isGrabbable(base3DOverlay->_isGrabbable) { setTransform(base3DOverlay->getTransform()); @@ -125,13 +127,19 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { bool needRenderItemUpdate = false; auto drawInFront = properties["drawInFront"]; - if (drawInFront.isValid()) { bool value = drawInFront.toBool(); setDrawInFront(value); needRenderItemUpdate = true; } + auto drawHUDLayer = properties["drawHUDLayer"]; + if (drawHUDLayer.isValid()) { + bool value = drawHUDLayer.toBool(); + setDrawHUDLayer(value); + needRenderItemUpdate = true; + } + auto isGrabbable = properties["grabbable"]; if (isGrabbable.isValid()) { setIsGrabbable(isGrabbable.toBool()); @@ -269,10 +277,10 @@ void Base3DOverlay::update(float duration) { // then the correct transform used for rendering is computed in the update transaction and assigned. if (_renderTransformDirty) { auto itemID = getRenderItemID(); - // Capture the render transform value in game loop before - auto latestTransform = evalRenderTransform(); - _renderTransformDirty = false; if (render::Item::isValidID(itemID)) { + // Capture the render transform value in game loop before + auto latestTransform = evalRenderTransform(); + _renderTransformDirty = false; render::ScenePointer scene = qApp->getMain3DScene(); render::Transaction transaction; transaction.updateItem(itemID, [latestTransform](Overlay& data) { @@ -282,8 +290,6 @@ void Base3DOverlay::update(float duration) { } }); scene->enqueueTransaction(transaction); - } else { - setRenderTransform(latestTransform); } } } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 3e65f163e2..d90caf5c4d 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -41,6 +41,7 @@ public: bool getIsSolidLine() const { return !_isDashedLine; } bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } bool getDrawInFront() const { return _drawInFront; } + bool getDrawHUDLayer() const { return _drawHUDLayer; } bool getIsGrabbable() const { return _isGrabbable; } void setLineWidth(float lineWidth) { _lineWidth = lineWidth; } @@ -48,6 +49,7 @@ public: void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } virtual void setDrawInFront(bool value) { _drawInFront = value; } + virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; } void setIsGrabbable(bool value) { _isGrabbable = value; } virtual AABox getBounds() const override = 0; @@ -81,6 +83,7 @@ protected: bool _isDashedLine; bool _ignoreRayIntersection; bool _drawInFront; + bool _drawHUDLayer; bool _isGrabbable { false }; mutable bool _renderTransformDirty{ true }; diff --git a/interface/src/ui/overlays/Billboard3DOverlay.cpp b/interface/src/ui/overlays/Billboard3DOverlay.cpp index 960f0de095..f19426b054 100644 --- a/interface/src/ui/overlays/Billboard3DOverlay.cpp +++ b/interface/src/ui/overlays/Billboard3DOverlay.cpp @@ -37,6 +37,14 @@ QVariant Billboard3DOverlay::getProperty(const QString &property) { return Planar3DOverlay::getProperty(property); } +void Billboard3DOverlay::update(float duration) { + // Billboard's transform needs to constantly be adjusted if it faces the avatar + // if (isFacingAvatar()) { + notifyRenderTransformChange(); + // } + Base3DOverlay::update(duration); +} + bool Billboard3DOverlay::applyTransformTo(Transform& transform, bool force) { bool transformChanged = false; if (force || usecTimestampNow() > _transformExpiry) { diff --git a/interface/src/ui/overlays/Billboard3DOverlay.h b/interface/src/ui/overlays/Billboard3DOverlay.h index 6b3aa40451..6f590dd6ec 100644 --- a/interface/src/ui/overlays/Billboard3DOverlay.h +++ b/interface/src/ui/overlays/Billboard3DOverlay.h @@ -26,6 +26,8 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; + void update(float deltatime) override; + protected: virtual bool applyTransformTo(Transform& transform, bool force = false) override; diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 4e51844d21..536b2c764f 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -263,7 +263,7 @@ const render::ShapeKey Circle3DOverlay::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } - if (!getIsSolid() || shouldDrawHUDLayer()) { + if (!getIsSolid()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 39fd4f9377..8cbb214857 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -41,6 +41,7 @@ ContextOverlayInterface::ContextOverlayInterface() { _entityPropertyFlags += PROP_MARKETPLACE_ID; _entityPropertyFlags += PROP_DIMENSIONS; _entityPropertyFlags += PROP_REGISTRATION_POINT; + _entityPropertyFlags += PROP_CERTIFICATE_ID; auto entityTreeRenderer = DependencyManager::get().data(); connect(entityTreeRenderer, SIGNAL(mousePressOnEntity(const EntityItemID&, const PointerEvent&)), this, SLOT(createOrDestroyContextOverlay(const EntityItemID&, const PointerEvent&))); @@ -176,7 +177,12 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& bool ContextOverlayInterface::contextOverlayFilterPassed(const EntityItemID& entityItemID) { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags); - return (entityProperties.getMarketplaceID().length() != 0); + Setting::Handle _settingSwitch{ "commerce", false }; + if (_settingSwitch.get()) { + return (entityProperties.getCertificateID().length() != 0); + } else { + return (entityProperties.getMarketplaceID().length() != 0); + } } bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) { diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 0ac9dba34b..b6df1dbc31 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -113,7 +113,7 @@ const render::ShapeKey Cube3DOverlay::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } - if (!getIsSolid() || shouldDrawHUDLayer()) { + if (!getIsSolid()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 998cc312eb..d0024d9710 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -132,7 +132,7 @@ void Image3DOverlay::render(RenderArgs* args) { const render::ShapeKey Image3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); - if (_emissive || shouldDrawHUDLayer()) { + if (_emissive) { builder.withUnlit(); } if (isTransparent()) { diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 4f5ea2042f..2a13249958 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -87,6 +87,10 @@ void ModelOverlay::update(float deltatime) { _drawInFrontDirty = false; _model->setLayeredInFront(getDrawInFront(), scene); } + if (_drawInHUDDirty) { + _drawInHUDDirty = false; + _model->setLayeredInHUD(getDrawHUDLayer(), scene); + } scene->enqueueTransaction(transaction); } @@ -111,6 +115,11 @@ void ModelOverlay::setDrawInFront(bool drawInFront) { _drawInFrontDirty = true; } +void ModelOverlay::setDrawHUDLayer(bool drawHUDLayer) { + Base3DOverlay::setDrawHUDLayer(drawHUDLayer); + _drawInHUDDirty = true; +} + void ModelOverlay::setProperties(const QVariantMap& properties) { auto origPosition = getPosition(); auto origRotation = getRotation(); diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 93deebac43..ea0eff170c 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -51,6 +51,7 @@ public: void setVisible(bool visible) override; void setDrawInFront(bool drawInFront) override; + void setDrawHUDLayer(bool drawHUDLayer) override; protected: Transform evalRenderTransform() override; @@ -78,8 +79,6 @@ private: bool _scaleToFit = { false }; float _loadPriority { 0.0f }; - bool _visibleDirty { false }; - bool _drawInFrontDirty { false }; AnimationPointer _animation; QUrl _animationURL; @@ -97,6 +96,11 @@ private: QUrl _jointMappingURL; bool _jointMappingCompleted { false }; QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints + + bool _visibleDirty { false }; + bool _drawInFrontDirty { false }; + bool _drawInHUDDirty { false }; + }; #endif // hifi_ModelOverlay_h diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 8b88d1e963..01ad56f20e 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -32,7 +32,6 @@ Overlay::Overlay() : _colorPulse(0.0f), _color(DEFAULT_OVERLAY_COLOR), _visible(true), - _drawHUDLayer(false), _anchor(NO_ANCHOR) { } @@ -51,7 +50,6 @@ Overlay::Overlay(const Overlay* overlay) : _colorPulse(overlay->_colorPulse), _color(overlay->_color), _visible(overlay->_visible), - _drawHUDLayer(overlay->_drawHUDLayer), _anchor(overlay->_anchor) { } @@ -90,11 +88,6 @@ void Overlay::setProperties(const QVariantMap& properties) { setColorPulse(properties["colorPulse"].toFloat()); } - if (properties["drawHUDLayer"].isValid()) { - bool drawHUDLayer = properties["drawHUDLayer"].toBool(); - setDrawHUDLayer(drawHUDLayer); - } - if (properties["visible"].isValid()) { bool visible = properties["visible"].toBool(); setVisible(visible); @@ -170,13 +163,6 @@ float Overlay::getAlpha() { return (_alphaPulse >= 0.0f) ? _alpha * pulseLevel : _alpha * (1.0f - pulseLevel); } -void Overlay::setDrawHUDLayer(bool drawHUDLayer) { - if (drawHUDLayer != _drawHUDLayer) { - qApp->getOverlays().setOverlayDrawHUDLayer(getOverlayID(), drawHUDLayer); - _drawHUDLayer = drawHUDLayer; - } -} - // pulse travels from min to max, then max to min in one period. float Overlay::updatePulse() { if (_pulsePeriod <= 0.0f) { diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 775c597397..39208f01a0 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -58,7 +58,6 @@ public: virtual bool is3D() const = 0; bool isLoaded() { return _isLoaded; } bool getVisible() const { return _visible; } - bool shouldDrawHUDLayer() const { return _drawHUDLayer; } virtual bool isTransparent() { return getAlphaPulse() != 0.0f || getAlpha() != 1.0f; }; xColor getColor(); float getAlpha(); @@ -117,7 +116,6 @@ protected: xColor _color; bool _visible; // should the overlay be drawn at all - bool _drawHUDLayer; // should the overlay be drawn on the HUD layer Anchor _anchor; unsigned int _stackOrder { 0 }; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index c93d225718..b59bcdb9b2 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -37,29 +37,22 @@ #include "Web3DOverlay.h" #include -#include "render/ShapePipeline.h" - Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); void Overlays::cleanupAllOverlays() { QMap overlaysHUD; - QMap overlays3DHUD; QMap overlaysWorld; { QMutexLocker locker(&_mutex); overlaysHUD.swap(_overlaysHUD); - overlays3DHUD.swap(_overlays3DHUD); overlaysWorld.swap(_overlaysWorld); } foreach(Overlay::Pointer overlay, overlaysHUD) { _overlaysToDelete.push_back(overlay); } - foreach(Overlay::Pointer overlay, overlays3DHUD) { - _overlaysToDelete.push_back(overlay); - } foreach(Overlay::Pointer overlay, overlaysWorld) { _overlaysToDelete.push_back(overlay); } @@ -73,8 +66,6 @@ void Overlays::init() { #if OVERLAY_PANELS _scriptEngine = new QScriptEngine(); #endif - _shapePlumber = std::make_shared(); - initOverlay3DPipelines(*_shapePlumber, true); } void Overlays::update(float deltatime) { @@ -83,9 +74,6 @@ void Overlays::update(float deltatime) { foreach(const auto& thisOverlay, _overlaysHUD) { thisOverlay->update(deltatime); } - foreach(const auto& thisOverlay, _overlays3DHUD) { - thisOverlay->update(deltatime); - } foreach(const auto& thisOverlay, _overlaysWorld) { thisOverlay->update(deltatime); } @@ -142,23 +130,6 @@ void Overlays::renderHUD(RenderArgs* renderArgs) { } } -void Overlays::render3DHUDOverlays(RenderArgs* renderArgs) { - PROFILE_RANGE(render_overlays, __FUNCTION__); - gpu::Batch& batch = *renderArgs->_batch; - - auto textureCache = DependencyManager::get(); - - QMutexLocker lock(&_mutex); - foreach(Overlay::Pointer thisOverlay, _overlays3DHUD) { - // Reset necessary batch pipeline settings between overlays - batch.setResourceTexture(0, textureCache->getWhiteTexture()); // FIXME - do we really need to do this?? - batch.setModelTransform(Transform()); - - renderArgs->_shapePipeline = _shapePlumber->pickPipeline(renderArgs, thisOverlay->getShapeKey()); - thisOverlay->render(renderArgs); - } -} - void Overlays::disable() { _enabled = false; } @@ -173,8 +144,6 @@ Overlay::Pointer Overlays::getOverlay(OverlayID id) const { QMutexLocker locker(&_mutex); if (_overlaysHUD.contains(id)) { return _overlaysHUD[id]; - } else if (_overlays3DHUD.contains(id)) { - return _overlays3DHUD[id]; } else if (_overlaysWorld.contains(id)) { return _overlaysWorld[id]; } @@ -232,7 +201,7 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { OverlayID thisID = OverlayID(QUuid::createUuid()); overlay->setOverlayID(thisID); overlay->setStackOrder(_stackOrder++); - if (overlay->is3D() && !overlay->shouldDrawHUDLayer()) { + if (overlay->is3D()) { { QMutexLocker locker(&_mutex); _overlaysWorld[thisID] = overlay; @@ -242,9 +211,6 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { render::Transaction transaction; overlay->addToScene(overlay, scene, transaction); scene->enqueueTransaction(transaction); - } else if (overlay->is3D() && overlay->shouldDrawHUDLayer()) { - QMutexLocker locker(&_mutex); - _overlays3DHUD[thisID] = overlay; } else { QMutexLocker locker(&_mutex); _overlaysHUD[thisID] = overlay; @@ -253,28 +219,6 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { return thisID; } -void Overlays::setOverlayDrawHUDLayer(const OverlayID& id, const bool drawHUDLayer) { - QMutexLocker locker(&_mutex); - if (drawHUDLayer && _overlaysWorld.contains(id)) { - std::shared_ptr overlay = _overlaysWorld.take(id); - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - auto itemID = overlay->getRenderItemID(); - if (render::Item::isValidID(itemID)) { - overlay->removeFromScene(overlay, scene, transaction); - scene->enqueueTransaction(transaction); - } - _overlays3DHUD[id] = overlay; - } else if (!drawHUDLayer && _overlays3DHUD.contains(id)) { - std::shared_ptr overlay = _overlays3DHUD.take(id); - render::ScenePointer scene = qApp->getMain3DScene(); - render::Transaction transaction; - overlay->addToScene(overlay, scene, transaction); - scene->enqueueTransaction(transaction); - _overlaysWorld[id] = overlay; - } -} - OverlayID Overlays::cloneOverlay(OverlayID id) { if (QThread::currentThread() != thread()) { OverlayID result; @@ -361,8 +305,6 @@ void Overlays::deleteOverlay(OverlayID id) { QMutexLocker locker(&_mutex); if (_overlaysHUD.contains(id)) { overlayToDelete = _overlaysHUD.take(id); - } else if (_overlays3DHUD.contains(id)) { - overlayToDelete = _overlays3DHUD.take(id); } else if (_overlaysWorld.contains(id)) { overlayToDelete = _overlaysWorld.take(id); } else { @@ -771,7 +713,7 @@ bool Overlays::isAddedOverlay(OverlayID id) { } QMutexLocker locker(&_mutex); - return _overlaysHUD.contains(id) || _overlays3DHUD.contains(id) || _overlaysWorld.contains(id); + return _overlaysHUD.contains(id) || _overlaysWorld.contains(id); } void Overlays::sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { @@ -799,13 +741,6 @@ void Overlays::sendHoverLeaveOverlay(const OverlayID& id, const PointerEvent& ev } OverlayID Overlays::getKeyboardFocusOverlay() { - if (QThread::currentThread() != thread()) { - OverlayID result; - PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "getKeyboardFocusOverlay", Q_RETURN_ARG(OverlayID, result)); - return result; - } - return qApp->getKeyboardFocusOverlay(); } diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 1e85562485..732a437eae 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -90,7 +90,6 @@ public: void init(); void update(float deltatime); void renderHUD(RenderArgs* renderArgs); - void render3DHUDOverlays(RenderArgs* renderArgs); void disable(); void enable(); @@ -103,8 +102,6 @@ public: OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); } OverlayID addOverlay(const Overlay::Pointer& overlay); - void setOverlayDrawHUDLayer(const OverlayID& id, const bool drawHUDLayer); - bool mousePressEvent(QMouseEvent* event); bool mouseDoublePressEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event); @@ -334,11 +331,8 @@ private: mutable QMutex _mutex { QMutex::Recursive }; QMap _overlaysHUD; - QMap _overlays3DHUD; QMap _overlaysWorld; - render::ShapePlumberPointer _shapePlumber; - #if OVERLAY_PANELS QMap _panels; #endif diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 887bf7ff8e..194c530df0 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -34,7 +34,8 @@ namespace render { template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay) { auto builder = ItemKey::Builder().withTypeShape(); if (overlay->is3D()) { - if (std::static_pointer_cast(overlay)->getDrawInFront()) { + auto overlay3D = std::static_pointer_cast(overlay); + if (overlay3D->getDrawInFront() || overlay3D->getDrawHUDLayer()) { builder.withLayered(); } if (overlay->isTransparent()) { @@ -49,20 +50,17 @@ namespace render { return overlay->getBounds(); } template <> int payloadGetLayer(const Overlay::Pointer& overlay) { - // Magic number while we are defining the layering mechanism: - const int LAYER_2D = 2; - const int LAYER_3D_FRONT = 1; - const int LAYER_3D = 0; - if (overlay->is3D()) { auto overlay3D = std::dynamic_pointer_cast(overlay); if (overlay3D->getDrawInFront()) { - return LAYER_3D_FRONT; + return Item::LAYER_3D_FRONT; + } else if (overlay3D->getDrawHUDLayer()) { + return Item::LAYER_3D_HUD; } else { - return LAYER_3D; + return Item::LAYER_3D; } } else { - return LAYER_2D; + return Item::LAYER_2D; } } template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args) { diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 2c1df478f6..a3b51d40bf 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -55,7 +55,7 @@ const render::ShapeKey Shape3DOverlay::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } - if (!getIsSolid() || shouldDrawHUDLayer()) { + if (!getIsSolid()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index f2c9968687..c9fc25b252 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -60,7 +60,7 @@ const render::ShapeKey Sphere3DOverlay::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } - if (!getIsSolid() || shouldDrawHUDLayer()) { + if (!getIsSolid()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index 57e3c32060..2e55a9a471 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -88,6 +88,7 @@ void Text3DOverlay::update(float deltatime) { applyTransformTo(transform); setTransform(transform); } + Parent::update(deltatime); } void Text3DOverlay::render(RenderArgs* args) { diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 98a7fba566..8f9f84642f 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -40,6 +40,7 @@ #include "scripting/HMDScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/MenuScriptingInterface.h" +#include "scripting/SettingsScriptingInterface.h" #include #include #include "FileDialogHelper.h" @@ -243,9 +244,9 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("InputConfiguration", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); + _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get()); - _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); diff --git a/libraries/audio/src/AudioDynamics.h b/libraries/audio/src/AudioDynamics.h index 08daa21348..b7c168bfab 100644 --- a/libraries/audio/src/AudioDynamics.h +++ b/libraries/audio/src/AudioDynamics.h @@ -31,6 +31,9 @@ #define MULQ31(a,b) ((int32_t)(MUL64(a, b) >> 31)) #define MULDIV64(a,b,c) (int32_t)(MUL64(a, b) / (c)) +#define ADDMOD32(a,b) (int32_t)((uint32_t)(a) + (uint32_t)(b)) +#define SUBMOD32(a,b) (int32_t)((uint32_t)(a) - (uint32_t)(b)) + // // on x86 architecture, assume that SSE2 is present // @@ -394,19 +397,21 @@ public: // Fast FIR attack/lowpass filter using a 2-stage CIC filter. // The step response reaches final value after N-1 samples. + // NOTE: CIC integrators intentionally overflow, using modulo arithmetic. + // See E. B. Hogenauer, "An economical class of digital filters for decimation and interpolation" const int32_t CICGAIN = 0xffffffff / (CIC1 * CIC2); // Q32 x = MULHI(x, CICGAIN); _buffer[i] = _acc1; - _acc1 += x; // integrator + _acc1 = ADDMOD32(_acc1, x); // integrator i = (i + CIC1 - 1) & MASK; - x = _acc1 - _buffer[i]; // comb + x = SUBMOD32(_acc1, _buffer[i]); // comb _buffer[i] = _acc2; - _acc2 += x; // integrator + _acc2 = ADDMOD32(_acc2, x); // integrator i = (i + CIC2 - 1) & MASK; - x = _acc2 - _buffer[i]; // comb + x = SUBMOD32(_acc2, _buffer[i]); // comb _index = (i + 1) & MASK; // skip unused tap return x; @@ -459,19 +464,21 @@ public: // Fast FIR attack/lowpass filter using a 2-stage CIC filter. // The step response reaches final value after N-1 samples. + // NOTE: CIC integrators intentionally overflow, using modulo arithmetic. + // See E. B. Hogenauer, "An economical class of digital filters for decimation and interpolation" const int32_t CICGAIN = 0xffffffff / (CIC1 * CIC2); // Q32 x = MULHI(x, CICGAIN); _buffer[i] = _acc1; - _acc1 += x; // integrator + _acc1 = ADDMOD32(_acc1, x); // integrator i = (i + CIC1 - 1) & MASK; - x = _acc1 - _buffer[i]; // comb + x = SUBMOD32(_acc1, _buffer[i]); // comb _buffer[i] = _acc2; - _acc2 += x; // integrator + _acc2 = ADDMOD32(_acc2, x); // integrator i = (i + CIC2 - 1) & MASK; - x = _acc2 - _buffer[i]; // comb + x = SUBMOD32(_acc2, _buffer[i]); // comb _index = (i + 1) & MASK; // skip unused tap return x; diff --git a/libraries/audio/src/AudioGate.cpp b/libraries/audio/src/AudioGate.cpp index 5b2561da07..13c794b923 100644 --- a/libraries/audio/src/AudioGate.cpp +++ b/libraries/audio/src/AudioGate.cpp @@ -6,12 +6,12 @@ // Copyright 2017 High Fidelity, Inc. // -#include "AudioGate.h" - #include +#include #include -#include + #include "AudioDynamics.h" +#include "AudioGate.h" // log2 domain headroom bits above 0dB (int32_t) static const int LOG2_HEADROOM_Q30 = 1; @@ -418,7 +418,7 @@ void GateMono::process(int16_t* input, int16_t* output, int numFrames) { _dc.process(x); // peak detect - int32_t peak = std::abs(x); + int32_t peak = abs(x); // convert to log2 domain peak = fixlog2(peak); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 664ef64bde..89fe270af1 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -381,7 +381,7 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QStringList jointNames READ getJointNames) - Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) + Q_PROPERTY(QUuid sessionUUID READ getSessionUUID NOTIFY sessionUUIDChanged) Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix) Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix) @@ -670,13 +670,19 @@ public: signals: void displayNameChanged(); void lookAtSnappingChanged(bool enabled); + void sessionUUIDChanged(); public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); void setJointMappingsFromNetworkReply(); - void setSessionUUID(const QUuid& sessionUUID) { setID(sessionUUID); } + void setSessionUUID(const QUuid& sessionUUID) { + if (sessionUUID != getID()) { + setID(sessionUUID); + emit sessionUUIDChanged(); + } + } virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index b1d2815f65..f448375f0d 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -74,6 +74,7 @@ public: void setModelTransform(const Transform& transform) { _modelTransform = transform; } const Transform& getModelTransform() const { return _modelTransform; } + glm::mat4 getUiTransform() const; float getAlpha() const { return _alpha; } void setAlpha(float alpha) { if (alpha != _alpha) { emit alphaChanged(); _alpha = alpha; } } @@ -122,7 +123,6 @@ protected slots: void sendFakeMouseEvent(); private: - glm::mat4 getUiTransform() const; void updateTooltips(); DisplayPluginPointer _currentDisplayPlugin; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index dc49a04880..b1b4c22712 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -307,7 +307,7 @@ bool OpenGLDisplayPlugin::activate() { auto compositorHelper = DependencyManager::get(); connect(compositorHelper.data(), &CompositorHelper::alphaChanged, [this] { auto compositorHelper = DependencyManager::get(); - auto animation = new QPropertyAnimation(this, "overlayAlpha"); + auto animation = new QPropertyAnimation(this, "hudAlpha"); animation->setDuration(200); animation->setEndValue(compositorHelper->getAlpha()); animation->start(); @@ -415,7 +415,7 @@ void OpenGLDisplayPlugin::customizeContext() { state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _overlayPipeline = gpu::Pipeline::create(program, state); + _hudPipeline = gpu::Pipeline::create(program, state); } { @@ -437,7 +437,7 @@ void OpenGLDisplayPlugin::customizeContext() { void OpenGLDisplayPlugin::uncustomizeContext() { _presentPipeline.reset(); _cursorPipeline.reset(); - _overlayPipeline.reset(); + _hudPipeline.reset(); _compositeFramebuffer.reset(); withPresentThreadLock([&] { _currentFrame.reset(); @@ -562,22 +562,23 @@ void OpenGLDisplayPlugin::updateFrameData() { }); } -void OpenGLDisplayPlugin::compositeOverlay() { - render([&](gpu::Batch& batch){ - batch.enableStereo(false); - batch.setFramebuffer(_compositeFramebuffer); - batch.setPipeline(_overlayPipeline); - batch.setResourceTexture(0, _currentFrame->overlay); - if (isStereo()) { - for_each_eye([&](Eye eye) { - batch.setViewportTransform(eyeViewport(eye)); +std::function OpenGLDisplayPlugin::getHUDOperator() { + return [this](gpu::Batch& batch, const gpu::TexturePointer& hudTexture) { + if (_hudPipeline) { + batch.enableStereo(false); + batch.setPipeline(_hudPipeline); + batch.setResourceTexture(0, hudTexture); + if (isStereo()) { + for_each_eye([&](Eye eye) { + batch.setViewportTransform(eyeViewport(eye)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + } else { + batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize())); batch.draw(gpu::TRIANGLE_STRIP, 4); - }); - } else { - batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize())); - batch.draw(gpu::TRIANGLE_STRIP, 4); + } } - }); + }; } void OpenGLDisplayPlugin::compositePointer() { @@ -626,29 +627,15 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeScene(); } - // Clear the depth framebuffer after drawing the scene so that the HUD elements can depth test against each other - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(_compositeFramebuffer); - batch.clearDepthFramebuffer((float) UINT32_MAX); - }); - #ifdef HIFI_ENABLE_NSIGHT_DEBUG - if (false) // do not compositeoverlay if running nsight debug + if (false) // do not draw the HUD if running nsight debug #endif { - PROFILE_RANGE_EX(render_detail, "compositeOverlay", 0xff0077ff, (uint64_t)presentCount()) - compositeOverlay(); - } - - // Only render HUD layer 3D overlays in HMD mode - if (isHmd()) { - PROFILE_RANGE_EX(render_detail, "compositeHUDOverlays", 0xff0077ff, (uint64_t)presentCount()) - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(_compositeFramebuffer); + PROFILE_RANGE_EX(render_detail, "handleHUDBatch", 0xff0077ff, (uint64_t)presentCount()) + auto hudOperator = getHUDOperator(); + withPresentThreadLock([&] { + _hudOperator = hudOperator; }); - _gpuContext->executeBatch(_currentFrame->postCompositeBatch); } { @@ -656,13 +643,7 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeExtra(); } - // Clear the depth buffer again and draw the pointer last so it's on top of everything - render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(_compositeFramebuffer); - batch.clearDepthFramebuffer((float) UINT32_MAX); - }); - + // Draw the pointer last so it's on top of everything auto compositorHelper = DependencyManager::get(); if (compositorHelper->getReticleVisible()) { PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount()) @@ -844,7 +825,7 @@ void OpenGLDisplayPlugin::assertIsPresentThread() const { bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) { withNonPresentThreadLock([&] { - _compositeOverlayAlpha = _overlayAlpha; + _compositeHUDAlpha = _hudAlpha; }); return Parent::beginFrameRender(frameIndex); } @@ -887,8 +868,7 @@ OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { void OpenGLDisplayPlugin::updateCompositeFramebuffer() { auto renderSize = getRecommendedRenderSize(); if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { - auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); - _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, depthFormat, renderSize.x, renderSize.y)); + _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); } } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 51f4a33bbd..ec775aed4c 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -22,6 +22,8 @@ #include #include +#include + namespace gpu { namespace gl { class GLBackend; @@ -30,7 +32,7 @@ namespace gpu { class OpenGLDisplayPlugin : public DisplayPlugin { Q_OBJECT - Q_PROPERTY(float overlayAlpha MEMBER _overlayAlpha) + Q_PROPERTY(float hudAlpha MEMBER _hudAlpha) using Parent = DisplayPlugin; protected: using Mutex = std::mutex; @@ -93,7 +95,7 @@ protected: virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; } virtual void compositeLayers(); virtual void compositeScene(); - virtual void compositeOverlay(); + virtual std::function getHUDOperator(); virtual void compositePointer(); virtual void compositeExtra() {}; @@ -137,12 +139,12 @@ protected: gpu::FramePointer _currentFrame; gpu::Frame* _lastFrame { nullptr }; gpu::FramebufferPointer _compositeFramebuffer; - gpu::PipelinePointer _overlayPipeline; + gpu::PipelinePointer _hudPipeline; gpu::PipelinePointer _simplePipeline; gpu::PipelinePointer _presentPipeline; gpu::PipelinePointer _cursorPipeline; gpu::TexturePointer _displayTexture{}; - float _compositeOverlayAlpha { 1.0f }; + float _compositeHUDAlpha { 1.0f }; struct CursorData { QImage image; @@ -176,6 +178,6 @@ protected: // Any resource shared by the main thread and the presentation thread must // be serialized through this mutex mutable Mutex _presentMutex; - float _overlayAlpha{ 1.0f }; + float _hudAlpha{ 1.0f }; }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 88ec94eefb..0785226836 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -33,6 +33,9 @@ #include "../Logging.h" #include "../CompositorHelper.h" +#include "render-utils/hmd_ui_vert.h" +#include "render-utils/hmd_ui_frag.h" + static const QString MONO_PREVIEW = "Mono Preview"; static const QString DISABLE_PREVIEW = "Disable Preview"; static const QString FRAMERATE = DisplayPlugin::MENU_PATH() + ">Framerate"; @@ -46,19 +49,11 @@ static const glm::mat4 IDENTITY_MATRIX; //#define LIVE_SHADER_RELOAD 1 extern glm::vec3 getPoint(float yaw, float pitch); -static QString readFile(const QString& filename) { - QFile file(filename); - file.open(QFile::Text | QFile::ReadOnly); - QString result; - result.append(QTextStream(&file).readAll()); - return result; -} - glm::uvec2 HmdDisplayPlugin::getRecommendedUiSize() const { return CompositorHelper::VIRTUAL_SCREEN_SIZE; } -QRect HmdDisplayPlugin::getRecommendedOverlayRect() const { +QRect HmdDisplayPlugin::getRecommendedHUDRect() const { return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; } @@ -108,7 +103,7 @@ void HmdDisplayPlugin::internalDeactivate() { void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); - _overlayRenderer.build(); + _hudRenderer.build(); } void HmdDisplayPlugin::uncustomizeContext() { @@ -121,7 +116,7 @@ void HmdDisplayPlugin::uncustomizeContext() { batch.setFramebuffer(_compositeFramebuffer); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0)); }); - _overlayRenderer = OverlayRenderer(); + _hudRenderer = HUDRenderer(); _previewTexture.reset(); Parent::uncustomizeContext(); } @@ -171,7 +166,7 @@ float HmdDisplayPlugin::getLeftCenterPixel() const { void HmdDisplayPlugin::internalPresent() { PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) - // Composite together the scene, overlay and mouse cursor + // Composite together the scene, hud and mouse cursor hmdPresent(); if (_displayTexture) { @@ -344,17 +339,9 @@ void HmdDisplayPlugin::updateFrameData() { auto correction = glm::inverse(batchPose) * currentPose; getGLBackend()->setCameraCorrection(correction); } - - auto compositorHelper = DependencyManager::get(); - glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); - - for_each_eye([&](Eye eye) { - auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; - _overlayRenderer.mvps[eye] = _eyeProjections[eye] * modelView; - }); } -void HmdDisplayPlugin::OverlayRenderer::build() { +void HmdDisplayPlugin::HUDRenderer::build() { vertices = std::make_shared(); indices = std::make_shared(); @@ -410,38 +397,20 @@ void HmdDisplayPlugin::OverlayRenderer::build() { format = std::make_shared(); // 1 for everyone format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - uniformBuffers[0] = std::make_shared(sizeof(Uniforms), nullptr); - uniformBuffers[1] = std::make_shared(sizeof(Uniforms), nullptr); + uniformsBuffer = std::make_shared(sizeof(Uniforms), nullptr); updatePipeline(); } -void HmdDisplayPlugin::OverlayRenderer::updatePipeline() { - static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui.vert"; - static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui.frag"; - -#if LIVE_SHADER_RELOAD - static qint64 vsBuiltAge = 0; - static qint64 fsBuiltAge = 0; - QFileInfo vsInfo(vsFile); - QFileInfo fsInfo(fsFile); - auto vsAge = vsInfo.lastModified().toMSecsSinceEpoch(); - auto fsAge = fsInfo.lastModified().toMSecsSinceEpoch(); - if (!pipeline || vsAge > vsBuiltAge || fsAge > fsBuiltAge) { - vsBuiltAge = vsAge; - fsBuiltAge = fsAge; -#else +void HmdDisplayPlugin::HUDRenderer::updatePipeline() { if (!pipeline) { -#endif - QString vsSource = readFile(vsFile); - QString fsSource = readFile(fsFile); - auto vs = gpu::Shader::createVertex(vsSource.toLocal8Bit().toStdString()); - auto ps = gpu::Shader::createPixel(fsSource.toLocal8Bit().toStdString()); + auto vs = gpu::Shader::createVertex(std::string(hmd_ui_vert)); + auto ps = gpu::Shader::createPixel(std::string(hmd_ui_frag)); auto program = gpu::Shader::createProgram(vs, ps); gpu::gl::GLBackend::makeProgram(*program, gpu::Shader::BindingSet()); - this->uniformsLocation = program->getUniformBuffers().findLocation("overlayBuffer"); + uniformsLocation = program->getUniformBuffers().findLocation("hudBuffer"); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false, false, gpu::LESS_EQUAL)); + state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL)); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); @@ -450,30 +419,29 @@ void HmdDisplayPlugin::OverlayRenderer::updatePipeline() { } } -void HmdDisplayPlugin::OverlayRenderer::render(HmdDisplayPlugin& plugin) { +std::function HmdDisplayPlugin::HUDRenderer::render(HmdDisplayPlugin& plugin) { updatePipeline(); - for_each_eye([&](Eye eye){ - uniforms.mvp = mvps[eye]; - uniformBuffers[eye]->setSubData(0, uniforms); - }); - plugin.render([&](gpu::Batch& batch) { - batch.enableStereo(false); - batch.setFramebuffer(plugin._compositeFramebuffer); - batch.setPipeline(pipeline); - batch.setInputFormat(format); - gpu::BufferView posView(vertices, VERTEX_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::POSITION)._element); - gpu::BufferView uvView(vertices, TEXTURE_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::TEXCOORD)._element); - batch.setInputBuffer(gpu::Stream::POSITION, posView); - batch.setInputBuffer(gpu::Stream::TEXCOORD, uvView); - batch.setIndexBuffer(gpu::UINT16, indices, 0); - batch.setResourceTexture(0, plugin._currentFrame->overlay); - // FIXME use stereo information input to set both MVPs in the uniforms - for_each_eye([&](Eye eye) { - batch.setUniformBuffer(uniformsLocation, uniformBuffers[eye]); - batch.setViewportTransform(plugin.eyeViewport(eye)); + return [this](gpu::Batch& batch, const gpu::TexturePointer& hudTexture) { + if (pipeline) { + batch.setPipeline(pipeline); + + batch.setInputFormat(format); + gpu::BufferView posView(vertices, VERTEX_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::POSITION)._element); + gpu::BufferView uvView(vertices, TEXTURE_OFFSET, vertices->getSize(), VERTEX_STRIDE, format->getAttributes().at(gpu::Stream::TEXCOORD)._element); + batch.setInputBuffer(gpu::Stream::POSITION, posView); + batch.setInputBuffer(gpu::Stream::TEXCOORD, uvView); + batch.setIndexBuffer(gpu::UINT16, indices, 0); + + uniformsBuffer->setSubData(0, uniforms); + batch.setUniformBuffer(uniformsLocation, uniformsBuffer); + + auto compositorHelper = DependencyManager::get(); + batch.setModelTransform(compositorHelper->getUiTransform()); + batch.setResourceTexture(0, hudTexture); + batch.drawIndexed(gpu::TRIANGLES, indexCount); - }); - }); + } + }; } void HmdDisplayPlugin::compositePointer() { @@ -500,12 +468,8 @@ void HmdDisplayPlugin::compositePointer() { }); } -void HmdDisplayPlugin::compositeOverlay() { - if (!_currentFrame || !_currentFrame->overlay) { - return; - } - - _overlayRenderer.render(*this); +std::function HmdDisplayPlugin::getHUDOperator() { + return _hudRenderer.render(*this); } HmdDisplayPlugin::~HmdDisplayPlugin() { diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index db4e0ff7a1..a7a6d2938d 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -33,7 +33,7 @@ public: glm::uvec2 getRecommendedRenderSize() const override final { return _renderTargetSize; } bool isDisplayVisible() const override { return isHmdMounted(); } - QRect getRecommendedOverlayRect() const override final; + QRect getRecommendedHUDRect() const override final; virtual glm::mat4 getHeadPose() const override; @@ -53,7 +53,7 @@ protected: bool internalActivate() override; void internalDeactivate() override; - void compositeOverlay() override; + std::function getHUDOperator() override; void compositePointer() override; void internalPresent() override; void customizeContext() override; @@ -91,7 +91,7 @@ private: gpu::TexturePointer _previewTexture; glm::vec2 _lastWindowSize; - struct OverlayRenderer { + struct HUDRenderer { gpu::Stream::FormatPointer format; gpu::BufferPointer vertices; gpu::BufferPointer indices; @@ -99,12 +99,9 @@ private: gpu::PipelinePointer pipeline; int32_t uniformsLocation { -1 }; - // FIXME this is stupid, use the built in transformation pipeline - std::array uniformBuffers; - std::array mvps; + gpu::BufferPointer uniformsBuffer; struct Uniforms { - mat4 mvp; float alpha { 1.0f }; } uniforms; @@ -119,6 +116,6 @@ private: void build(); void updatePipeline(); - void render(HmdDisplayPlugin& plugin); - } _overlayRenderer; + std::function render(HmdDisplayPlugin& plugin); + } _hudRenderer; }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 67fcc5cc69..92a82908e2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -72,7 +72,11 @@ EntityRendererPointer EntityTreeRenderer::renderableForEntityId(const EntityItem render::ItemID EntityTreeRenderer::renderableIdForEntityId(const EntityItemID& id) const { auto renderable = renderableForEntityId(id); - return renderable ? renderable->getRenderItemID() : render::Item::INVALID_ITEM_ID; + if (renderable) { + return renderable->getRenderItemID(); + } else { + return render::Item::INVALID_ITEM_ID; + } } int EntityTreeRenderer::_entitiesScriptEngineCount = 0; @@ -222,16 +226,6 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene _renderablesToUpdate.insert({ entityId, renderable }); } - // NOTE: Looping over all the entity renderers is likely to be a bottleneck in the future - // Currently, this is necessary because the model entity loading logic requires constant polling - // This was working fine because the entity server used to send repeated updates as your view changed, - // but with the improved entity server logic (PR 11141), updateInScene (below) would not be triggered enough - for (const auto& entry : _entitiesInScene) { - const auto& renderable = entry.second; - if (renderable) { - renderable->update(scene, transaction); - } - } if (!_renderablesToUpdate.empty()) { for (const auto& entry : _renderablesToUpdate) { const auto& renderable = entry.second; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index ea514d3181..c38d106bfa 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -291,18 +291,6 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans }); } -void EntityRenderer::update(const ScenePointer& scene, Transaction& transaction) { - if (!isValidRenderItem()) { - return; - } - - if (!needsUpdate()) { - return; - } - - doUpdate(scene, transaction, _entity); -} - // // Internal methods // @@ -316,11 +304,6 @@ bool EntityRenderer::needsRenderUpdate() const { return needsRenderUpdateFromEntity(_entity); } -// Returns true if the item needs to have update called -bool EntityRenderer::needsUpdate() const { - return needsUpdateFromEntity(_entity); -} - // Returns true if the item in question needs to have updateInScene called because of changes in the entity bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity) const { bool success = false; @@ -346,25 +329,27 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity return false; } -void EntityRenderer::doRenderUpdateAsynchronous(const EntityItemPointer& entity) { - auto transparent = isTransparent(); - if (_prevIsTransparent && !transparent) { - _isFading = false; - } - _prevIsTransparent = transparent; +void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { + withWriteLock([&] { + auto transparent = isTransparent(); + if (_prevIsTransparent && !transparent) { + _isFading = false; + } + _prevIsTransparent = transparent; - bool success = false; - auto bound = entity->getAABox(success); - if (success) { - _bound = bound; - } - auto newModelTransform = entity->getTransformToCenter(success); - if (success) { - _modelTransform = newModelTransform; - } + bool success = false; + auto bound = entity->getAABox(success); + if (success) { + _bound = bound; + } + auto newModelTransform = entity->getTransformToCenter(success); + if (success) { + _modelTransform = newModelTransform; + } - _moving = entity->isMovingRelativeToParent(); - _visible = entity->getVisible(); + _moving = entity->isMovingRelativeToParent(); + _visible = entity->getVisible(); + }); } void EntityRenderer::onAddToScene(const EntityItemPointer& entity) { diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 56cb39252f..34dbceb643 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -49,8 +49,6 @@ public: virtual bool addToScene(const ScenePointer& scene, Transaction& transaction) final; virtual void removeFromScene(const ScenePointer& scene, Transaction& transaction); - virtual void update(const ScenePointer& scene, Transaction& transaction); - protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); @@ -73,22 +71,15 @@ protected: // Returns true if the item in question needs to have updateInScene called because of changes in the entity virtual bool needsRenderUpdateFromEntity(const EntityItemPointer& entity) const; - // Returns true if the item in question needs to have update called - virtual bool needsUpdate() const; - - // Returns true if the item in question needs to have update called because of changes in the entity - virtual bool needsUpdateFromEntity(const EntityItemPointer& entity) const { return false; } - // Will be called on the main thread from updateInScene. This can be used to fetch things like // network textures or model geometry from resource caches - virtual void doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { } + virtual void doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity); // Will be called by the lambda posted to the scene in updateInScene. // This function will execute on the rendering thread, so you cannot use network caches to fetch // data in this method if using multi-threaded rendering - virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity); - - virtual void doUpdate(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { } + + virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity) { } // Called by the `render` method after `needsRenderUpdate` virtual void doRender(RenderArgs* args) = 0; @@ -158,15 +149,6 @@ protected: onRemoveFromSceneTyped(_typedEntity); } - using Parent::needsUpdateFromEntity; - // Returns true if the item in question needs to have update called because of changes in the entity - virtual bool needsUpdateFromEntity(const EntityItemPointer& entity) const override final { - if (Parent::needsUpdateFromEntity(entity)) { - return true; - } - return needsUpdateFromTypedEntity(_typedEntity); - } - using Parent::needsRenderUpdateFromEntity; // Returns true if the item in question needs to have updateInScene called because of changes in the entity virtual bool needsRenderUpdateFromEntity(const EntityItemPointer& entity) const override final { @@ -181,11 +163,6 @@ protected: doRenderUpdateSynchronousTyped(scene, transaction, _typedEntity); } - virtual void doUpdate(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) override final { - Parent::doUpdate(scene, transaction, entity); - doUpdateTyped(scene, transaction, _typedEntity); - } - virtual void doRenderUpdateAsynchronous(const EntityItemPointer& entity) override final { Parent::doRenderUpdateAsynchronous(entity); doRenderUpdateAsynchronousTyped(_typedEntity); @@ -194,8 +171,6 @@ protected: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { return false; } virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { } virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { } - virtual bool needsUpdateFromTypedEntity(const TypedEntityPointer& entity) const { return false; } - virtual void doUpdateTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { } virtual void onAddToSceneTyped(const TypedEntityPointer& entity) { } virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) { } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d1e47fd906..9120cd1788 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1026,16 +1026,12 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { entity->copyAnimationJointDataToModel(); } -bool ModelEntityRenderer::needsUpdate() const { +bool ModelEntityRenderer::needsRenderUpdate() const { ModelPointer model; withReadLock([&] { model = _model; }); - if (_modelJustLoaded) { - return true; - } - if (model) { if (_needsJointSimulation || _moving || _animating) { return true; @@ -1061,10 +1057,10 @@ bool ModelEntityRenderer::needsUpdate() const { return true; } } - return Parent::needsUpdate(); + return Parent::needsRenderUpdate(); } -bool ModelEntityRenderer::needsUpdateFromTypedEntity(const TypedEntityPointer& entity) const { +bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (resultWithReadLock([&] { if (entity->hasModel() != _hasModel) { return true; @@ -1126,7 +1122,7 @@ bool ModelEntityRenderer::needsUpdateFromTypedEntity(const TypedEntityPointer& e return false; } -void ModelEntityRenderer::doUpdateTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { +void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { if (_hasModel != entity->hasModel()) { _hasModel = entity->hasModel(); } @@ -1152,11 +1148,11 @@ void ModelEntityRenderer::doUpdateTyped(const ScenePointer& scene, Transaction& return; } - _modelJustLoaded = false; // Check for addition if (_hasModel && !(bool)_model) { model = std::make_shared(nullptr, entity.get()); - connect(model.get(), &Model::setURLFinished, this, &ModelEntityRenderer::handleModelLoaded); + connect(model.get(), &Model::setURLFinished, this, &ModelEntityRenderer::requestRenderUpdate); + connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity)); model->init(); entity->setModel(model); @@ -1244,12 +1240,7 @@ void ModelEntityRenderer::doUpdateTyped(const ScenePointer& scene, Transaction& mapJoints(entity, model->getJointNames()); } animate(entity); - } -} - -void ModelEntityRenderer::handleModelLoaded(bool success) { - if (success) { - _modelJustLoaded = true; + emit requestRenderUpdate(); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index ad0afeee0a..d1424316e9 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -138,10 +138,10 @@ protected: virtual ItemKey getKey() override; virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) override; - virtual bool needsUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; - virtual bool needsUpdate() const override; + virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; + virtual bool needsRenderUpdate() const override; virtual void doRender(RenderArgs* args) override; - virtual void doUpdateTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; + virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; private: void animate(const TypedEntityPointer& entity); @@ -151,7 +151,6 @@ private: // Transparency is handled in ModelMeshPartPayload virtual bool isTransparent() const override { return false; } - bool _modelJustLoaded { false }; bool _hasModel { false }; ::ModelPointer _model; GeometryResource::Pointer _compoundShapeResource; @@ -180,8 +179,6 @@ private: uint64_t _lastAnimated { 0 }; float _currentFrame { 0 }; -private slots: - void handleModelLoaded(bool success); }; } } // namespace diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 8fcf2c090d..4028f105c8 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -88,31 +88,33 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } _color = vec4(toGlm(entity->getXColor()), entity->getLocalRenderAlpha()); + + _shape = entity->getShape(); + _position = entity->getPosition(); + _dimensions = entity->getDimensions(); + _orientation = entity->getOrientation(); + + if (_shape == entity::Sphere) { + _modelTransform.postScale(SPHERE_ENTITY_SCALE); + } + + _modelTransform.postScale(_dimensions); }); } void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - if (_procedural.isEnabled() && _procedural.isFading()) { - float isFading = Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f; - _procedural.setIsFading(isFading); - } - - _shape = entity->getShape(); - _position = entity->getPosition(); - _dimensions = entity->getDimensions(); - _orientation = entity->getOrientation(); - - if (_shape == entity::Sphere) { - _modelTransform.postScale(SPHERE_ENTITY_SCALE); - } - - _modelTransform.postScale(_dimensions); + withReadLock([&] { + if (_procedural.isEnabled() && _procedural.isFading()) { + float isFading = Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f; + _procedural.setIsFading(isFading); + } + }); } bool ShapeEntityRenderer::isTransparent() const { if (_procedural.isEnabled() && _procedural.isFading()) { return Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f; - } + } // return _entity->getLocalRenderAlpha() < 1.0f || Parent::isTransparent(); return Parent::isTransparent(); @@ -126,15 +128,16 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; - auto geometryShape = MAPPING[_shape]; - batch.setModelTransform(_modelTransform); // use a transform with scale, rotation, registration point and translation - + GeometryCache::Shape geometryShape; bool proceduralRender = false; - glm::vec4 outColor = _color; + glm::vec4 outColor; withReadLock([&] { + geometryShape = MAPPING[_shape]; + batch.setModelTransform(_modelTransform); // use a transform with scale, rotation, registration point and translation + outColor = _color; if (_procedural.isReady()) { _procedural.prepare(batch, _position, _dimensions, _orientation); - auto outColor = _procedural.getColor(_color); + outColor = _procedural.getColor(_color); outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; proceduralRender = true; } @@ -149,13 +152,13 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { } } else { // FIXME, support instanced multi-shape rendering using multidraw indirect - _color.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; + outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; auto geometryCache = DependencyManager::get(); - auto pipeline = _color.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); + auto pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { - geometryCache->renderWireShapeInstance(args, batch, geometryShape, _color, pipeline); + geometryCache->renderWireShapeInstance(args, batch, geometryShape, outColor, pipeline); } else { - geometryCache->renderSolidShapeInstance(args, batch, geometryShape, _color, pipeline); + geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline); } } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 0561fa5130..4688ef5d2b 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -118,31 +118,30 @@ void WebEntityRenderer::onTimeout() { } void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { - // This work must be done on the main thread - if (!hasWebSurface()) { - buildWebSurface(entity); - } + withWriteLock([&] { + // This work must be done on the main thread + if (!hasWebSurface()) { + buildWebSurface(entity); + } - if (_contextPosition != entity->getPosition()) { - // update globalPosition - _contextPosition = entity->getPosition(); - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); - } + if (_contextPosition != entity->getPosition()) { + // update globalPosition + _contextPosition = entity->getPosition(); + _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); + } - if (_lastSourceUrl != entity->getSourceUrl()) { - _lastSourceUrl = entity->getSourceUrl(); - loadSourceURL(); - } + if (_lastSourceUrl != entity->getSourceUrl()) { + _lastSourceUrl = entity->getSourceUrl(); + loadSourceURL(); + } - _lastDPI = entity->getDPI(); + _lastDPI = entity->getDPI(); - glm::vec2 windowSize = getWindowSize(entity); - _webSurface->resize(QSize(windowSize.x, windowSize.y)); -} + glm::vec2 windowSize = getWindowSize(entity); + _webSurface->resize(QSize(windowSize.x, windowSize.y)); -void WebEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - Parent::doRenderUpdateAsynchronousTyped(entity); - _modelTransform.postScale(entity->getDimensions()); + _modelTransform.postScale(entity->getDimensions()); + }); } void WebEntityRenderer::doRender(RenderArgs* args) { @@ -180,7 +179,9 @@ void WebEntityRenderer::doRender(RenderArgs* args) { static const glm::vec2 texMin(0.0f), texMax(1.0f), topLeft(-0.5f), bottomRight(0.5f); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(_modelTransform); + withReadLock([&] { + batch.setModelTransform(_modelTransform); + }); batch.setResourceTexture(0, _texture); float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio); @@ -190,7 +191,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { } bool WebEntityRenderer::hasWebSurface() { - return resultWithReadLock([&] { return (bool)_webSurface; }); + return (bool)_webSurface; } bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { @@ -213,11 +214,8 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { }; { - QSharedPointer webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); - webSurface->create(); - withWriteLock([&] { - _webSurface = webSurface; - }); + _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); + _webSurface->create(); } // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces @@ -322,33 +320,31 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con } void WebEntityRenderer::loadSourceURL() { - withWriteLock([&] { - const QUrl sourceUrl(_lastSourceUrl); - if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || - _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { - _contentType = htmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/")); + const QUrl sourceUrl(_lastSourceUrl); + if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" || + _lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) { + _contentType = htmlContent; + _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/")); - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(YOUTUBE_MAX_FPS); - } else { - _webSurface->setMaxFps(DEFAULT_MAX_FPS); - } - - _webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty("url", _lastSourceUrl); - }); + // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. + if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) { + _webSurface->setMaxFps(YOUTUBE_MAX_FPS); } else { - _contentType = qmlContent; - _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); - _webSurface->load(_lastSourceUrl); - if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - } + _webSurface->setMaxFps(DEFAULT_MAX_FPS); } - }); + + _webSurface->load("WebEntityView.qml", [this](QQmlContext* context, QObject* item) { + item->setProperty("url", _lastSourceUrl); + }); + } else { + _contentType = qmlContent; + _webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath())); + _webSurface->load(_lastSourceUrl); + if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") { + auto tabletScriptingInterface = DependencyManager::get(); + tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); + } + } } void WebEntityRenderer::handlePointerEvent(const TypedEntityPointer& entity, const PointerEvent& event) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index a67eb39670..4b7e7e25a1 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -29,7 +29,6 @@ protected: virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; - virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; virtual void doRender(RenderArgs* args) override; virtual bool isTransparent() const override; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index bf37a08386..fa386ae090 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -864,6 +864,12 @@ void EntityTree::findEntities(const ViewFrustum& frustum, QVector& foundEntities) { + recurseTreeWithOperation(elementFilter, nullptr); +} + EntityItemPointer EntityTree::findEntityByID(const QUuid& id) { EntityItemID entityID(id); return findEntityByEntityItemID(entityID); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d0448f438a..53e36bc7c7 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -165,6 +165,11 @@ public: /// \param foundEntities[out] vector of EntityItemPointer void findEntities(const ViewFrustum& frustum, QVector& foundEntities); + /// finds all entities that match scanOperator + /// \parameter scanOperator function that scans entities that match criteria + /// \parameter foundEntities[out] vector of EntityItemPointer + void findEntities(RecurseOctreeOperation& scanOperator, QVector& foundEntities); + void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 0c33855a61..2696377028 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -869,6 +869,14 @@ void EntityTreeElement::getEntities(const ViewFrustum& frustum, QVector& foundEntities) { + forEachEntity([&](EntityItemPointer entity) { + if (filter(entity)) { + foundEntities.push_back(entity); + } + }); +} + EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) const { EntityItemPointer foundEntity = NULL; withReadLock([&] { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index c7fb80c330..cafae9941a 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -27,6 +27,7 @@ class EntityTreeElement; using EntityItems = QVector; using EntityTreeElementWeakPointer = std::weak_ptr; using EntityTreeElementPointer = std::shared_ptr; +using EntityItemFilter = std::function; class EntityTreeUpdateArgs { public: @@ -199,6 +200,11 @@ public: /// \param entities[out] vector of non-const EntityItemPointer void getEntities(const ViewFrustum& frustum, QVector& foundEntities); + /// finds all entities that match filter + /// \param filter function that adds matching entities to foundEntities + /// \param entities[out] vector of non-const EntityItemPointer + void getEntities(EntityItemFilter& filter, QVector& foundEntities); + EntityItemPointer getEntityWithID(uint32_t id) const; EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 14813a68fe..9c3ce47886 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -497,7 +497,7 @@ bool ModelEntityItem::hasModel() const { }); } bool ModelEntityItem::hasCompoundShapeURL() const { - return _compoundShapeURL.get().isEmpty(); + return !_compoundShapeURL.get().isEmpty(); } QString ModelEntityItem::getModelURL() const { diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 2ca22d5dc7..41e9893f64 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -19,6 +19,7 @@ bool GLTexelFormat::isCompressed() const { case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: return true; default: return false; @@ -94,6 +95,11 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { result = GL_R11F_G11F_B10F; break; + case gpu::RGB9E5: + // the type should be float + result = GL_RGB9_E5; + break; + case gpu::DEPTH: result = GL_DEPTH_COMPONENT32; switch (dstFormat.getType()) { @@ -286,6 +292,9 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::COMPRESSED_BC5_XY: result = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC6_RGB: + result = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; case gpu::COMPRESSED_BC7_SRGBA: result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; @@ -438,6 +447,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC5_XY: texel.internalFormat = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC6_RGB: + texel.internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; @@ -537,10 +549,16 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::R11G11B10: texel.format = GL_RGB; - // the type should be float + texel.type = GL_UNSIGNED_INT_10F_11F_11F_REV; texel.internalFormat = GL_R11F_G11F_B10F; break; + case gpu::RGB9E5: + texel.format = GL_RGB; + texel.type = GL_UNSIGNED_INT_5_9_9_9_REV; + texel.internalFormat = GL_RGB9_E5; + break; + case gpu::DEPTH: texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it texel.internalFormat = GL_DEPTH_COMPONENT32; @@ -779,6 +797,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC5_XY: texel.internalFormat = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC6_RGB: + texel.internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 5998aebb33..cde4ff5f75 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -114,6 +114,7 @@ Size GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: glCompressedTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; @@ -131,6 +132,7 @@ Size GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: glCompressedTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index d8b3968ed8..795a630ccd 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -143,6 +143,7 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; @@ -158,6 +159,7 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: if (glCompressedTextureSubImage2DEXT) { auto target = GLTexture::CUBE_FACE_LAYOUT[face]; glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat, diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 77d22258b2..fca220c224 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -92,7 +92,7 @@ public: void captureNamedDrawCallInfo(std::string name); Batch(); - explicit Batch(const Batch& batch); + Batch(const Batch& batch); ~Batch(); void clear(); diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index b15f8d929f..7efe4d3ed6 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -24,11 +24,13 @@ const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_B const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA }; const Element Element::COLOR_COMPRESSED_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA }; const Element Element::COLOR_COMPRESSED_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY }; -const Element Element::COLOR_COMPRESSED_SRGBA_HIGH { TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; +const Element Element::COLOR_COMPRESSED_SRGBA_HIGH{ TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; +const Element Element::COLOR_COMPRESSED_HDR_RGB{ TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB }; const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY }; const Element Element::COLOR_R11G11B10{ SCALAR, FLOAT, R11G11B10 }; +const Element Element::COLOR_RGB9E5{ SCALAR, FLOAT, RGB9E5 }; const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA }; const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index c4d88236da..0654b23581 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -187,11 +187,13 @@ enum Semantic : uint8_t { COMPRESSED_BC3_SRGBA, COMPRESSED_BC4_RED, COMPRESSED_BC5_XY, + COMPRESSED_BC6_RGB, COMPRESSED_BC7_SRGBA, _LAST_COMPRESSED, R11G11B10, + RGB9E5, UNIFORM, UNIFORM_BUFFER, @@ -240,11 +242,13 @@ static const int SEMANTIC_SIZE_FACTOR[NUM_SEMANTICS] = { 16, //COMPRESSED_BC3_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes 8, //COMPRESSED_BC4_RED, 1/2 byte/pixel * 4x4 pixels = 8 bytes 16, //COMPRESSED_BC5_XY, 1 byte/pixel * 4x4 pixels = 16 bytes + 16, //COMPRESSED_BC6_RGB, 1 byte/pixel * 4x4 pixels = 16 bytes 16, //COMPRESSED_BC7_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes 1, //_LAST_COMPRESSED, 1, //R11G11B10, + 1, //RGB9E5 1, //UNIFORM, 1, //UNIFORM_BUFFER, @@ -306,12 +310,14 @@ public: static const Element COLOR_BGRA_32; static const Element COLOR_SBGRA_32; static const Element COLOR_R11G11B10; + static const Element COLOR_RGB9E5; static const Element COLOR_COMPRESSED_RED; static const Element COLOR_COMPRESSED_SRGB; static const Element COLOR_COMPRESSED_SRGBA_MASK; static const Element COLOR_COMPRESSED_SRGBA; static const Element COLOR_COMPRESSED_XY; static const Element COLOR_COMPRESSED_SRGBA_HIGH; + static const Element COLOR_COMPRESSED_HDR_RGB; static const Element VEC2NU8_XY; static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index 69872906e3..1ed77a26b7 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -32,14 +32,10 @@ namespace gpu { Mat4 pose; /// The collection of batches which make up the frame Batches batches; - /// Single batch containing overlays to be drawn in the composite framebuffer - Batch postCompositeBatch; /// The main thread updates to buffers that are applicable for this frame. BufferUpdates bufferUpdates; /// The destination framebuffer in which the results will be placed FramebufferPointer framebuffer; - /// The destination texture containing the 2D overlay - TexturePointer overlay; /// How to process the framebuffer when the frame dies. MUST BE THREAD SAFE FramebufferRecycler framebufferRecycler; diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 4b836512c4..4a588c3c84 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -683,6 +684,21 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture"); + auto mipFormat = cubeTexture.getStoredMipFormat(); + std::function unpackFunc; + + switch (mipFormat.getSemantic()) { + case gpu::R11G11B10: + unpackFunc = glm::unpackF2x11_1x10; + break; + case gpu::RGB9E5: + unpackFunc = glm::unpackF3x9_E1x5; + break; + default: + assert(false); + break; + } + const uint sqOrder = order*order; // allocate memory for calculations @@ -716,17 +732,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { PROFILE_RANGE(render_gpu, "ProcessFace"); - auto mipFormat = cubeTexture.getStoredMipFormat(); - auto numComponents = mipFormat.getScalarCount(); - int roffset { 0 }; - int goffset { 1 }; - int boffset { 2 }; - if ((mipFormat.getSemantic() == gpu::BGRA) || (mipFormat.getSemantic() == gpu::SBGRA)) { - roffset = 2; - boffset = 0; - } - - auto data = cubeTexture.accessStoredMipFace(0, face)->readData(); + auto data = reinterpret_cast( cubeTexture.accessStoredMipFace(0, face)->readData() ); if (data == nullptr) { continue; } @@ -806,29 +812,24 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< // index of texel in texture - // get color from texture and map to range [0, 1] - float red { 0.0f }; - float green { 0.0f }; - float blue { 0.0f }; + // get color from texture + glm::vec3 color{ 0.0f, 0.0f, 0.0f }; for (int i = 0; i < stride; ++i) { for (int j = 0; j < stride; ++j) { - int k = (int)(x + i - halfStride + (y + j - halfStride) * width) * numComponents; - red += ColorUtils::sRGB8ToLinearFloat(data[k + roffset]); - green += ColorUtils::sRGB8ToLinearFloat(data[k + goffset]); - blue += ColorUtils::sRGB8ToLinearFloat(data[k + boffset]); + int k = (int)(x + i - halfStride + (y + j - halfStride) * width); + color += unpackFunc(data[k]); } } - glm::vec3 clr(red, green, blue); // scale color and add to previously accumulated coefficients // red - sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.r * fDiffSolid); + sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.r * fDiffSolid); sphericalHarmonicsAdd(resultR.data(), order, resultR.data(), shBuffB.data()); // green - sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.g * fDiffSolid); + sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.g * fDiffSolid); sphericalHarmonicsAdd(resultG.data(), order, resultG.data(), shBuffB.data()); // blue - sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.b * fDiffSolid); + sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.b * fDiffSolid); sphericalHarmonicsAdd(resultB.data(), order, resultB.data(), shBuffB.data()); } } diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 14fd983ec2..08fc4ec101 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -515,8 +515,6 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { return texture; } - - TexturePointer Texture::unserialize(const cache::FilePointer& cacheEntry) { std::unique_ptr ktxPointer = ktx::KTX::create(std::make_shared(cacheEntry->getFilepath().c_str())); if (!ktxPointer) { @@ -536,7 +534,7 @@ TexturePointer Texture::unserialize(const std::string& ktxfile) { if (!ktxPointer) { return nullptr; } - + auto texture = build(ktxPointer->toDescriptor()); if (texture) { texture->setKtxBacking(ktxfile); @@ -570,6 +568,12 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RG_RGTC2, ktx::GLBaseInternalFormat::RG); } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_HDR_RGB && mipFormat == Format::COLOR_COMPRESSED_HDR_RGB) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_RGB9E5 && mipFormat == Format::COLOR_RGB9E5) { + header.setUncompressed(ktx::GLType::UNSIGNED_INT_5_9_9_9_REV, 1, ktx::GLFormat::RGB, ktx::GLInternalFormat::RGB9_E5, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_R11G11B10 && mipFormat == Format::COLOR_R11G11B10) { + header.setUncompressed(ktx::GLType::UNSIGNED_INT_10F_11F_11F_REV, 1, ktx::GLFormat::RGB, ktx::GLInternalFormat::R11F_G11F_B10F, ktx::GLBaseInternalFormat::RGB); } else { return false; } @@ -612,6 +616,12 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E } else { return false; } + } else if (header.getGLFormat() == ktx::GLFormat::RGB && header.getGLType() == ktx::GLType::UNSIGNED_INT_10F_11F_11F_REV) { + mipFormat = Format::COLOR_R11G11B10; + texelFormat = Format::COLOR_R11G11B10; + } else if (header.getGLFormat() == ktx::GLFormat::RGB && header.getGLType() == ktx::GLType::UNSIGNED_INT_5_9_9_9_REV) { + mipFormat = Format::COLOR_RGB9E5; + texelFormat = Format::COLOR_RGB9E5; } else if (header.isCompressed()) { if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) { mipFormat = Format::COLOR_COMPRESSED_SRGB; @@ -631,6 +641,9 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) { mipFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; texelFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) { + mipFormat = Format::COLOR_COMPRESSED_HDR_RGB; + texelFormat = Format::COLOR_COMPRESSED_HDR_RGB; } else { return false; } diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index b4b5bdeae1..58b920da4d 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -11,9 +11,10 @@ #include "Image.h" +#include +#include + #include - - #include #include #include @@ -42,6 +43,8 @@ bool DEV_DECIMATE_TEXTURES = false; std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; +static const auto HDR_FORMAT = gpu::Element::COLOR_R11G11B10; + static std::atomic compressColorTextures { false }; static std::atomic compressNormalTextures { false }; static std::atomic compressGrayscaleTextures { false }; @@ -71,6 +74,8 @@ glm::uvec2 rectifyToSparseSize(const glm::uvec2& size) { namespace image { +QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB30; + TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { switch (type) { case ALBEDO_TEXTURE: @@ -213,6 +218,25 @@ void setCubeTexturesCompressionEnabled(bool enabled) { compressCubeTextures.store(enabled); } +static float denormalize(float value, const float minValue) { + return value < minValue ? 0.0f : value; +} + +uint32 packR11G11B10F(const glm::vec3& color) { + // Denormalize else unpacking gives high and incorrect values + // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value + static const auto minValue = 6.10e-5f; + static const auto maxValue = 6.50e4f; + glm::vec3 ucolor; + ucolor.r = denormalize(color.r, minValue); + ucolor.g = denormalize(color.g, minValue); + ucolor.b = denormalize(color.b, minValue); + ucolor.r = std::min(ucolor.r, maxValue); + ucolor.g = std::min(ucolor.g, maxValue); + ucolor.b = std::min(ucolor.b, maxValue); + return glm::packF2x11_1x10(ucolor); +} + gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType, const std::atomic& abortProcessing) { @@ -270,8 +294,6 @@ gpu::TexturePointer processImage(const QByteArray& content, const std::string& f return texture; } - - QImage processSourceImage(const QImage& srcImage, bool cubemap) { PROFILE_RANGE(resource_parse, "processSourceImage"); const glm::uvec2 srcImageSize = toGlm(srcImage.size()); @@ -303,8 +325,8 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) { } #if defined(NVTT_API) -struct MyOutputHandler : public nvtt::OutputHandler { - MyOutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} +struct OutputHandler : public nvtt::OutputHandler { + OutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { _size = size; @@ -313,12 +335,14 @@ struct MyOutputHandler : public nvtt::OutputHandler { _data = static_cast(malloc(size)); _current = _data; } + virtual bool writeData(const void* data, int size) override { assert(_current + size <= _data + _size); memcpy(_current, data, size); _current += size; return true; } + virtual void endImage() override { if (_face >= 0) { _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast(_data)); @@ -336,6 +360,51 @@ struct MyOutputHandler : public nvtt::OutputHandler { int _size = 0; int _face = -1; }; + +struct PackedFloatOutputHandler : public OutputHandler { + PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) { + if (format == gpu::Element::COLOR_RGB9E5) { + _packFunc = glm::packF3x9_E1x5; + } else if (format == gpu::Element::COLOR_R11G11B10) { + _packFunc = packR11G11B10F; + } else { + qCWarning(imagelogging) << "Unknown handler format"; + Q_UNREACHABLE(); + } + } + + virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { + // Divide by 3 because we will compress from 3*floats to 1 uint32 + OutputHandler::beginImage(size / 3, width, height, depth, face, miplevel); + } + virtual bool writeData(const void* data, int size) override { + // Expecting to write multiple of floats + if (_packFunc) { + assert((size % sizeof(float)) == 0); + auto floatCount = size / sizeof(float); + const float* floatBegin = (const float*)data; + const float* floatEnd = floatBegin + floatCount; + + while (floatBegin < floatEnd) { + _pixel[_coordIndex] = *floatBegin; + floatBegin++; + _coordIndex++; + if (_coordIndex == 3) { + uint32 packedRGB = _packFunc(_pixel); + _coordIndex = 0; + OutputHandler::writeData(&packedRGB, sizeof(packedRGB)); + } + } + return true; + } + return false; + } + + std::function _packFunc; + glm::vec3 _pixel; + int _coordIndex{ 0 }; +}; + struct MyErrorHandler : public nvtt::ErrorHandler { virtual void error(nvtt::Error e) override { qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e); @@ -343,10 +412,113 @@ struct MyErrorHandler : public nvtt::ErrorHandler { }; #endif -void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing = false, int face = -1) { -#if CPU_MIPMAPS - PROFILE_RANGE(resource_parse, "generateMips"); +class SequentialTaskDispatcher : public nvtt::TaskDispatcher { +public: + SequentialTaskDispatcher(const std::atomic& abortProcessing) : _abortProcessing(abortProcessing) {}; + const std::atomic& _abortProcessing; + + virtual void dispatch(nvtt::Task* task, void* context, int count) override { + for (int i = 0; i < count; i++) { + if (!_abortProcessing.load()) { + task(context, i); + } else { + break; + } + } + } +}; + +void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atomic& abortProcessing, int face) { + assert(image.format() == QIMAGE_HDR_FORMAT); + + const int width = image.width(), height = image.height(); + std::vector data; + std::vector::iterator dataIt; + auto mipFormat = texture->getStoredMipFormat(); + std::function unpackFunc; + + nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; + nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; + + nvtt::CompressionOptions compressionOptions; + compressionOptions.setQuality(nvtt::Quality_Production); + + if (mipFormat == gpu::Element::COLOR_COMPRESSED_HDR_RGB) { + compressionOptions.setFormat(nvtt::Format_BC6); + } else if (mipFormat == gpu::Element::COLOR_RGB9E5) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + } else if (mipFormat == gpu::Element::COLOR_R11G11B10) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + if (HDR_FORMAT == gpu::Element::COLOR_RGB9E5) { + unpackFunc = glm::unpackF3x9_E1x5; + } else if (HDR_FORMAT == gpu::Element::COLOR_R11G11B10) { + unpackFunc = glm::unpackF2x11_1x10; + } else { + qCWarning(imagelogging) << "Unknown HDR encoding format in QImage"; + Q_UNREACHABLE(); + return; + } + + data.resize(width*height); + dataIt = data.begin(); + for (auto lineNb = 0; lineNb < height; lineNb++) { + const uint32* srcPixelIt = reinterpret_cast( image.constScanLine(lineNb) ); + const uint32* srcPixelEnd = srcPixelIt + width; + + while (srcPixelIt < srcPixelEnd) { + *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f); + ++srcPixelIt; + ++dataIt; + } + } + assert(dataIt == data.end()); + + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHeader(false); + std::unique_ptr outputHandler; + MyErrorHandler errorHandler; + outputOptions.setErrorHandler(&errorHandler); + nvtt::Context context; + int mipLevel = 0; + + if (mipFormat == gpu::Element::COLOR_RGB9E5 || mipFormat == gpu::Element::COLOR_R11G11B10) { + // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats + outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); + } else { + outputHandler.reset( new OutputHandler(texture, face) ); + } + + outputOptions.setOutputHandler(outputHandler.get()); + + nvtt::Surface surface; + surface.setImage(inputFormat, width, height, 1, &(*data.begin())); + surface.setAlphaMode(alphaMode); + surface.setWrapMode(wrapMode); + + SequentialTaskDispatcher dispatcher(abortProcessing); + nvtt::Compressor compressor; + context.setTaskDispatcher(&dispatcher); + + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + while (surface.canMakeNextMipmap() && !abortProcessing.load()) { + surface.buildNextMipmap(nvtt::MipmapFilter_Box); + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + } +} + +void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing, int face) { if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } @@ -400,10 +572,10 @@ void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); inputGamma = 1.0f; outputGamma = 1.0f; } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { @@ -411,10 +583,10 @@ void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); inputGamma = 1.0f; outputGamma = 1.0f; } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { @@ -422,19 +594,19 @@ void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { compressionOptions.setFormat(nvtt::Format_RGBA); compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); } else if (mipFormat == gpu::Element::COLOR_R_8) { compressionOptions.setFormat(nvtt::Format_RGB); compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); @@ -454,32 +626,26 @@ void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); - MyOutputHandler outputHandler(texture, face); + OutputHandler outputHandler(texture, face); outputOptions.setOutputHandler(&outputHandler); MyErrorHandler errorHandler; outputOptions.setErrorHandler(&errorHandler); - class SequentialTaskDispatcher : public nvtt::TaskDispatcher { - public: - SequentialTaskDispatcher(const std::atomic& abortProcessing) : _abortProcessing(abortProcessing) {}; - - const std::atomic& _abortProcessing; - - virtual void dispatch(nvtt::Task* task, void* context, int count) override { - for (int i = 0; i < count; i++) { - if (!_abortProcessing.load()) { - task(context, i); - } else { - break; - } - } - } - }; - SequentialTaskDispatcher dispatcher(abortProcessing); nvtt::Compressor compressor; compressor.setTaskDispatcher(&dispatcher); compressor.process(inputOptions, compressionOptions, outputOptions); +} + +void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing = false, int face = -1) { +#if CPU_MIPMAPS + PROFILE_RANGE(resource_parse, "generateMips"); + + if (image.format() == QIMAGE_HDR_FORMAT) { + generateHDRMips(texture, image, abortProcessing, face); + } else { + generateLDRMips(texture, image, abortProcessing, face); + } #else texture->setAutoGenerateMips(true); #endif @@ -961,6 +1127,62 @@ const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { }; const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); +//#define DEBUG_COLOR_PACKING + +QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { + QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT); + std::function packFunc; +#ifdef DEBUG_COLOR_PACKING + std::function unpackFunc; +#endif + + switch (format.getSemantic()) { + case gpu::R11G11B10: + packFunc = packR11G11B10F; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF2x11_1x10; +#endif + break; + case gpu::RGB9E5: + packFunc = glm::packF3x9_E1x5; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF3x9_E1x5; +#endif + break; + default: + qCWarning(imagelogging) << "Unsupported HDR format"; + Q_UNREACHABLE(); + return srcImage; + } + + srcImage = srcImage.convertToFormat(QImage::Format_ARGB32); + for (auto y = 0; y < srcImage.height(); y++) { + const QRgb* srcLineIt = reinterpret_cast( srcImage.constScanLine(y) ); + const QRgb* srcLineEnd = srcLineIt + srcImage.width(); + uint32* hdrLineIt = reinterpret_cast( hdrImage.scanLine(y) ); + glm::vec3 color; + + while (srcLineIt < srcLineEnd) { + color.r = qRed(*srcLineIt); + color.g = qGreen(*srcLineIt); + color.b = qBlue(*srcLineIt); + // Normalize and apply gamma + color /= 255.0f; + color.r = powf(color.r, 2.2f); + color.g = powf(color.g, 2.2f); + color.b = powf(color.b, 2.2f); + *hdrLineIt = packFunc(color); +#ifdef DEBUG_COLOR_PACKING + glm::vec3 ucolor = unpackFunc(*hdrLineIt); + assert(glm::distance(color, ucolor) <= 5e-2); +#endif + ++srcLineIt; + ++hdrLineIt; + } + } + return hdrImage; +} + gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance, const std::atomic& abortProcessing) { @@ -969,18 +1191,19 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& gpu::TexturePointer theTexture = nullptr; if ((srcImage.width() > 0) && (srcImage.height() > 0)) { QImage image = processSourceImage(srcImage, true); - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH; - formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH; + formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; } else { - formatMip = gpu::Element::COLOR_SRGBA_32; - formatGPU = gpu::Element::COLOR_SRGBA_32; + formatMip = HDR_FORMAT; + formatGPU = HDR_FORMAT; + } + + if (image.format() != QIMAGE_HDR_FORMAT) { + image = convertToHDRFormat(image, HDR_FORMAT); } // Find the layout of the cubemap in the 2D image @@ -1028,9 +1251,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& // Generate irradiance while we are at it if (generateIrradiance) { PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(gpu::Element::COLOR_SRGBA_32, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(gpu::Element::COLOR_SBGRA_32); + irradianceTexture->setStoredMipFormat(HDR_FORMAT); for (uint8 face = 0; face < faces.size(); ++face) { irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } diff --git a/libraries/ktx/src/khronos/KHR.h b/libraries/ktx/src/khronos/KHR.h index 98cc1a4736..cda22513ee 100644 --- a/libraries/ktx/src/khronos/KHR.h +++ b/libraries/ktx/src/khronos/KHR.h @@ -209,6 +209,137 @@ namespace khronos { COMPRESSED_SIGNED_RG11_EAC = 0x9273, }; + inline uint8_t evalUncompressedBlockBitSize(InternalFormat format) { + switch (format) { + case InternalFormat::R8: + case InternalFormat::R8_SNORM: + return 8; + case InternalFormat::R16: + case InternalFormat::R16_SNORM: + case InternalFormat::RG8: + case InternalFormat::RG8_SNORM: + return 16; + case InternalFormat::RG16: + case InternalFormat::RG16_SNORM: + return 16; + case InternalFormat::R3_G3_B2: + return 8; + case InternalFormat::RGB4: + return 12; + case InternalFormat::RGB5: + case InternalFormat::RGB565: + return 16; + case InternalFormat::RGB8: + case InternalFormat::RGB8_SNORM: + return 24; + case InternalFormat::RGB10: + // TODO: check if this is really the case + return 32; + case InternalFormat::RGB12: + // TODO: check if this is really the case + return 48; + case InternalFormat::RGB16: + case InternalFormat::RGB16_SNORM: + return 48; + case InternalFormat::RGBA2: + return 8; + case InternalFormat::RGBA4: + case InternalFormat::RGB5_A1: + return 16; + case InternalFormat::RGBA8: + case InternalFormat::RGBA8_SNORM: + case InternalFormat::RGB10_A2: + case InternalFormat::RGB10_A2UI: + return 32; + case InternalFormat::RGBA12: + return 48; + case InternalFormat::RGBA16: + case InternalFormat::RGBA16_SNORM: + return 64; + case InternalFormat::SRGB8: + return 24; + case InternalFormat::SRGB8_ALPHA8: + return 32; + case InternalFormat::R16F: + return 16; + case InternalFormat::RG16F: + return 32; + case InternalFormat::RGB16F: + return 48; + case InternalFormat::RGBA16F: + return 64; + case InternalFormat::R32F: + return 32; + case InternalFormat::RG32F: + return 64; + case InternalFormat::RGB32F: + return 96; + case InternalFormat::RGBA32F: + return 128; + case InternalFormat::R11F_G11F_B10F: + case InternalFormat::RGB9_E5: + return 32; + case InternalFormat::R8I: + case InternalFormat::R8UI: + return 8; + case InternalFormat::R16I: + case InternalFormat::R16UI: + return 16; + case InternalFormat::R32I: + case InternalFormat::R32UI: + return 32; + case InternalFormat::RG8I: + case InternalFormat::RG8UI: + return 16; + case InternalFormat::RG16I: + case InternalFormat::RG16UI: + return 32; + case InternalFormat::RG32I: + case InternalFormat::RG32UI: + return 64; + case InternalFormat::RGB8I: + case InternalFormat::RGB8UI: + return 24; + case InternalFormat::RGB16I: + case InternalFormat::RGB16UI: + return 48; + case InternalFormat::RGB32I: + case InternalFormat::RGB32UI: + return 96; + case InternalFormat::RGBA8I: + case InternalFormat::RGBA8UI: + return 32; + case InternalFormat::RGBA16I: + case InternalFormat::RGBA16UI: + return 64; + case InternalFormat::RGBA32I: + case InternalFormat::RGBA32UI: + return 128; + case InternalFormat::DEPTH_COMPONENT16: + return 16; + case InternalFormat::DEPTH_COMPONENT24: + return 24; + case InternalFormat::DEPTH_COMPONENT32: + case InternalFormat::DEPTH_COMPONENT32F: + case InternalFormat::DEPTH24_STENCIL8: + return 32; + case InternalFormat::DEPTH32F_STENCIL8: + // TODO : check if this true + return 40; + case InternalFormat::STENCIL_INDEX1: + return 1; + case InternalFormat::STENCIL_INDEX4: + return 4; + case InternalFormat::STENCIL_INDEX8: + return 8; + case InternalFormat::STENCIL_INDEX16: + return 16; + + default: + return 0; + } + } + template inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) { enum { val = ALIGNMENT && !(ALIGNMENT & (ALIGNMENT - 1)) }; @@ -225,6 +356,7 @@ namespace khronos { case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: // BC3 case InternalFormat::COMPRESSED_RED_RGTC1: // BC4 case InternalFormat::COMPRESSED_RG_RGTC2: // BC5 + case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: // BC6 case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: // BC7 return evalAlignedCompressedBlockCount<4>(value); @@ -241,6 +373,7 @@ namespace khronos { return 8; case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case InternalFormat::COMPRESSED_RG_RGTC2: return 16; @@ -250,6 +383,10 @@ namespace khronos { } } + inline uint8_t evalCompressedBlockBitSize(InternalFormat format) { + return evalCompressedBlockSize(format) * 8; + } + enum class BaseInternalFormat : uint32_t { // GL 4.4 Table 8.11 DEPTH_COMPONENT = 0x1902, diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index 3b1a12cba7..5bd45549c1 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -54,15 +54,13 @@ uint32_t Header::evalPixelOrBlockDepth(uint32_t level) const { return evalMipDimension(level, getPixelDepth()); } -size_t Header::evalPixelOrBlockSize() const { +size_t Header::evalPixelOrBlockBitSize() const { size_t result = 0; + auto format = getGLInternaFormat(); if (isCompressed()) { - auto format = getGLInternaFormat(); - result = khronos::gl::texture::evalCompressedBlockSize(format); + result = khronos::gl::texture::evalCompressedBlockBitSize(format); } else { - // FIXME should really be using the internal format, not the base internal format - auto baseFormat = getGLBaseInternalFormat(); - result = khronos::gl::texture::evalComponentCount(baseFormat); + result = khronos::gl::texture::evalUncompressedBlockBitSize(format); } if (0 == result) { @@ -73,11 +71,14 @@ size_t Header::evalPixelOrBlockSize() const { size_t Header::evalRowSize(uint32_t level) const { auto pixWidth = evalPixelOrBlockWidth(level); - auto pixSize = evalPixelOrBlockSize(); + auto pixSize = evalPixelOrBlockBitSize(); if (pixSize == 0) { return 0; } - return evalPaddedSize(pixWidth * pixSize); + auto totalByteSize = pixWidth * pixSize; + // Round to the nearest upper byte size + totalByteSize = (totalByteSize / 8) + (((totalByteSize % 8) != 0) & 1); + return evalPaddedSize(totalByteSize); } size_t Header::evalFaceSize(uint32_t level) const { diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 8dc4ec7a47..54a8188a42 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -170,7 +170,7 @@ namespace ktx { uint32_t evalPixelOrBlockHeight(uint32_t level) const; uint32_t evalPixelOrBlockDepth(uint32_t level) const; - size_t evalPixelOrBlockSize() const; + size_t evalPixelOrBlockBitSize() const; size_t evalRowSize(uint32_t level) const; size_t evalFaceSize(uint32_t level) const; size_t evalImageSize(uint32_t level) const; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 2756334a1a..b62ad7b366 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -71,14 +71,14 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { } else { QUrl url = _url.resolved(filename); - QString texdir = mapping.value("texdir").toString(); + QString texdir = mapping.value(TEXDIR_FIELD).toString(); if (!texdir.isNull()) { if (!texdir.endsWith('/')) { texdir += '/'; } _textureBaseUrl = resolveTextureBaseUrl(url, _url.resolved(texdir)); } else { - _textureBaseUrl = _effectiveBaseURL; + _textureBaseUrl = url.resolved(QUrl(".")); } auto animGraphVariant = mapping.value("animGraphUrl"); @@ -241,8 +241,10 @@ private: }; void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { - _url = _effectiveBaseURL; - _textureBaseUrl = _effectiveBaseURL; + if (_url != _effectiveBaseURL) { + _url = _effectiveBaseURL; + _textureBaseUrl = _effectiveBaseURL; + } QThreadPool::globalInstance()->start(new GeometryReader(_self, _effectiveBaseURL, _mapping, data, _combineParts)); } diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 2dc22a9337..55d0ad4f75 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -155,6 +155,7 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) { case AssetRequest::Error::NoError: _data = req->getData(); _result = Success; + recordBytesDownloadedInStats(STAT_ATP_RESOURCE_TOTAL_BYTES, _data.size()); break; case AssetRequest::InvalidHash: _result = InvalidURL; @@ -202,9 +203,8 @@ void AssetResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytes emit progress(bytesReceived, bytesTotal); auto now = p_high_resolution_clock::now(); - - // Recording ATP bytes downloaded in stats - DependencyManager::get()->updateStat(STAT_ATP_RESOURCE_TOTAL_BYTES, bytesReceived); + + recordBytesDownloadedInStats(STAT_ATP_RESOURCE_TOTAL_BYTES, bytesReceived); // if we haven't received the full asset check if it is time to output progress to log // we do so every X seconds to assist with ATP download tracking diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h index 18b82f2573..ac36c83985 100644 --- a/libraries/networking/src/AssetResourceRequest.h +++ b/libraries/networking/src/AssetResourceRequest.h @@ -41,6 +41,8 @@ private: AssetRequest* _assetRequest { nullptr }; p_high_resolution_clock::time_point _lastProgressDebug; + + int64_t _lastRecordedBytesDownloaded { 0 }; }; #endif diff --git a/libraries/networking/src/FileResourceRequest.cpp b/libraries/networking/src/FileResourceRequest.cpp index 26857716e1..dfff21dae6 100644 --- a/libraries/networking/src/FileResourceRequest.cpp +++ b/libraries/networking/src/FileResourceRequest.cpp @@ -69,8 +69,7 @@ void FileResourceRequest::doSend() { if (_result == ResourceRequest::Success) { statTracker->incrementStat(STAT_FILE_REQUEST_SUCCESS); - // Recording FILE bytes downloaded in stats - statTracker->updateStat(STAT_FILE_RESOURCE_TOTAL_BYTES,fileSize); + statTracker->updateStat(STAT_FILE_RESOURCE_TOTAL_BYTES, fileSize); } else { statTracker->incrementStat(STAT_FILE_REQUEST_FAILED); } diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp index edba520bd5..a34f92aaa6 100644 --- a/libraries/networking/src/HTTPResourceRequest.cpp +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -141,6 +141,8 @@ void HTTPResourceRequest::onRequestFinished() { } } + recordBytesDownloadedInStats(STAT_HTTP_RESOURCE_TOTAL_BYTES, _data.size()); + break; case QNetworkReply::TimeoutError: @@ -201,11 +203,8 @@ void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesT _sendTimer->start(); emit progress(bytesReceived, bytesTotal); - - // Recording HTTP bytes downloaded in stats - DependencyManager::get()->updateStat(STAT_HTTP_RESOURCE_TOTAL_BYTES, bytesReceived); - - + + recordBytesDownloadedInStats(STAT_HTTP_RESOURCE_TOTAL_BYTES, bytesReceived); } void HTTPResourceRequest::onTimeout() { diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 574ec7f054..300a445ebd 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -676,7 +676,7 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t } // insert the new node and release our read lock -#ifdef Q_OS_ANDROID +#if defined(Q_OS_ANDROID) || (defined(__clang__) && defined(Q_OS_LINUX)) _nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); #else _nodeHash.emplace(newNode->getUUID(), newNodePointer); diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index f028535e2f..5d39583c9e 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -11,6 +11,9 @@ #include "ResourceRequest.h" +#include +#include + #include ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { } @@ -40,3 +43,11 @@ QString ResourceRequest::getResultString() const { default: return "Unspecified Error"; } } + +void ResourceRequest::recordBytesDownloadedInStats(const QString& statName, int64_t bytesReceived) { + auto dBytes = bytesReceived - _lastRecordedBytesDownloaded; + if (dBytes > 0) { + _lastRecordedBytesDownloaded = bytesReceived; + DependencyManager::get()->updateStat(statName, dBytes); + } +} diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index cf852c1e1b..8dd09ccc49 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -85,6 +85,7 @@ signals: protected: virtual void doSend() = 0; + void recordBytesDownloadedInStats(const QString& statName, int64_t bytesReceived); QUrl _url; QUrl _relativePathURL; @@ -97,6 +98,7 @@ protected: ByteRange _byteRange; bool _rangeRequestSuccessful { false }; uint64_t _totalSizeOfResource { 0 }; + int64_t _lastRecordedBytesDownloaded { 0 }; }; #endif diff --git a/libraries/plugins/src/plugins/DisplayPlugin.cpp b/libraries/plugins/src/plugins/DisplayPlugin.cpp index 20c72159c4..e43d5e76f3 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.cpp +++ b/libraries/plugins/src/plugins/DisplayPlugin.cpp @@ -33,4 +33,13 @@ void DisplayPlugin::waitForPresent() { break; } } +} + +std::function DisplayPlugin::getHUDOperator() { + std::function hudOperator; + { + QMutexLocker locker(&_presentMutex); + hudOperator = _hudOperator; + } + return hudOperator; } \ No newline at end of file diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index d3054c9bd8..11ca6f754a 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -156,8 +156,8 @@ public: return aspect(getRecommendedRenderSize()); } - // The recommended bounds for primary overlay placement - virtual QRect getRecommendedOverlayRect() const { + // The recommended bounds for primary HUD placement + virtual QRect getRecommendedHUDRect() const { const int DESKTOP_SCREEN_PADDING = 50; auto recommendedSize = getRecommendedUiSize() - glm::uvec2(DESKTOP_SCREEN_PADDING); return QRect(0, 0, recommendedSize.x, recommendedSize.y); @@ -204,8 +204,9 @@ public: void waitForPresent(); - static const QString& MENU_PATH(); + std::function getHUDOperator(); + static const QString& MENU_PATH(); signals: void recommendedFramebufferSizeChanged(const QSize& size); @@ -217,6 +218,8 @@ protected: gpu::ContextPointer _gpuContext; + std::function _hudOperator { std::function() }; + private: QMutex _presentMutex; QWaitCondition _presentCondition; diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 658eab7210..cf8e268681 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -68,8 +68,6 @@ static gpu::Stream::FormatPointer INSTANCED_SOLID_FADE_STREAM_FORMAT; static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3); -static const gpu::Type SHAPE_INDEX_TYPE = gpu::UINT32; -static const uint SHAPE_INDEX_SIZE = sizeof(gpu::uint32); template std::vector polygon() { @@ -84,67 +82,83 @@ std::vector polygon() { } void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices) { + gpu::Buffer::Size offset = vertexBuffer->getSize(); vertexBuffer->append(vertices); - _positionView = gpu::BufferView(vertexBuffer, 0, - vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); - _normalView = gpu::BufferView(vertexBuffer, SHAPE_NORMALS_OFFSET, - vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, NORMAL_ELEMENT); + gpu::Buffer::Size viewSize = vertices.size() * 2 * sizeof(glm::vec3); + + _positionView = gpu::BufferView(vertexBuffer, offset, + viewSize, SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); + _normalView = gpu::BufferView(vertexBuffer, offset + SHAPE_NORMALS_OFFSET, + viewSize, SHAPE_VERTEX_STRIDE, NORMAL_ELEMENT); } void GeometryCache::ShapeData::setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices) { - _indices = indexBuffer; + gpu::Buffer::Size offset = indexBuffer->getSize(); if (!indices.empty()) { - _indexOffset = indexBuffer->getSize() / SHAPE_INDEX_SIZE; - _indexCount = indices.size(); - indexBuffer->append(indices); + for (uint32_t i = 0; i < indices.size(); ++i) { + indexBuffer->append((uint16_t)indices[i]); + } } + gpu::Size viewSize = indices.size() * sizeof(uint16_t); + _indicesView = gpu::BufferView(indexBuffer, offset, viewSize, gpu::Element::INDEX_UINT16); + offset = indexBuffer->getSize(); if (!wireIndices.empty()) { - _wireIndexOffset = indexBuffer->getSize() / SHAPE_INDEX_SIZE; - _wireIndexCount = wireIndices.size(); - indexBuffer->append(wireIndices); + for (uint32_t i = 0; i < wireIndices.size(); ++i) { + indexBuffer->append((uint16_t)wireIndices[i]); + } } + viewSize = wireIndices.size() * sizeof(uint16_t); + _wireIndicesView = gpu::BufferView(indexBuffer, offset, viewSize, gpu::Element::INDEX_UINT16); } void GeometryCache::ShapeData::setupBatch(gpu::Batch& batch) const { batch.setInputBuffer(gpu::Stream::POSITION, _positionView); batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); - batch.setIndexBuffer(SHAPE_INDEX_TYPE, _indices, 0); + batch.setIndexBuffer(_indicesView); } void GeometryCache::ShapeData::draw(gpu::Batch& batch) const { - if (_indexCount) { + uint32_t numIndices = (uint32_t)_indicesView.getNumElements(); + if (numIndices > 0) { setupBatch(batch); - batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)_indexCount, (gpu::uint32)_indexOffset); + batch.drawIndexed(gpu::TRIANGLES, numIndices, 0); } } void GeometryCache::ShapeData::drawWire(gpu::Batch& batch) const { - if (_wireIndexCount) { - setupBatch(batch); - batch.drawIndexed(gpu::LINES, (gpu::uint32)_wireIndexCount, (gpu::uint32)_wireIndexOffset); + uint32_t numIndices = (uint32_t)_wireIndicesView.getNumElements(); + if (numIndices > 0) { + batch.setInputBuffer(gpu::Stream::POSITION, _positionView); + batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); + batch.setIndexBuffer(_wireIndicesView); + batch.drawIndexed(gpu::LINES, numIndices, 0); } } void GeometryCache::ShapeData::drawInstances(gpu::Batch& batch, size_t count) const { - if (_indexCount) { + uint32_t numIndices = (uint32_t)_indicesView.getNumElements(); + if (numIndices > 0) { setupBatch(batch); - batch.drawIndexedInstanced((gpu::uint32)count, gpu::TRIANGLES, (gpu::uint32)_indexCount, (gpu::uint32)_indexOffset); + batch.drawIndexedInstanced((gpu::uint32)count, gpu::TRIANGLES, numIndices, 0); } } void GeometryCache::ShapeData::drawWireInstances(gpu::Batch& batch, size_t count) const { - if (_wireIndexCount) { - setupBatch(batch); - batch.drawIndexedInstanced((gpu::uint32)count, gpu::LINES, (gpu::uint32)_wireIndexCount, (gpu::uint32)_wireIndexOffset); + uint32_t numIndices = (uint32_t)_wireIndicesView.getNumElements(); + if (numIndices > 0) { + batch.setInputBuffer(gpu::Stream::POSITION, _positionView); + batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); + batch.setIndexBuffer(_wireIndicesView); + batch.drawIndexedInstanced((gpu::uint32)count, gpu::LINES, numIndices, 0); } } static const size_t ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT = 3; size_t GeometryCache::getShapeTriangleCount(Shape shape) { - return _shapes[shape]._indexCount / VERTICES_PER_TRIANGLE; + return _shapes[shape]._indicesView.getNumElements() / VERTICES_PER_TRIANGLE; } size_t GeometryCache::getSphereTriangleCount() { @@ -168,7 +182,6 @@ static IndexPair indexToken(geometry::Index a, geometry::Index b) { template void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { using namespace geometry; - Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; IndexVector solidIndices, wireIndices; IndexPairs wireSeenIndices; @@ -179,6 +192,7 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& face = shape.faces[f]; // Compute the face normal @@ -219,7 +233,6 @@ void setupFlatShape(GeometryCache::ShapeData& shapeData, const geometry::Solid void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid& shape, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer) { using namespace geometry; - Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; vertices.reserve(shape.vertices.size() * 2); @@ -236,6 +249,7 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid solidIndices.reserve(faceIndexCount * faceCount); + Index baseVertex = 0; for (size_t f = 0; f < faceCount; f++) { const Face& face = shape.faces[f]; // Create the wire indices for unseen edges @@ -265,7 +279,6 @@ void setupSmoothShape(GeometryCache::ShapeData& shapeData, const geometry::Solid template void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexBuffer, gpu::BufferPointer& indexBuffer, bool isConical = false) { using namespace geometry; - Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; IndexVector solidIndices, wireIndices; @@ -286,6 +299,7 @@ void extrudePolygon(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& ver vertices.push_back(vec3(v.x, -0.5f, v.z)); vertices.push_back(vec3(0.0f, -1.0f, 0.0f)); } + Index baseVertex = 0; for (uint32_t i = 2; i < N; i++) { solidIndices.push_back(baseVertex + 0); solidIndices.push_back(baseVertex + i); @@ -343,7 +357,6 @@ void drawCircle(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexB // Draw a circle with radius 1/4th the size of the bounding box using namespace geometry; - Index baseVertex = (Index)(vertexBuffer->getSize() / SHAPE_VERTEX_STRIDE); VertexVector vertices; IndexVector solidIndices, wireIndices; const int NUM_CIRCLE_VERTICES = 64; @@ -354,6 +367,7 @@ void drawCircle(GeometryCache::ShapeData& shapeData, gpu::BufferPointer& vertexB vertices.push_back(vec3(0.0f, 0.0f, 0.0f)); } + Index baseVertex = 0; for (uint32_t i = 2; i < NUM_CIRCLE_VERTICES; i++) { solidIndices.push_back(baseVertex + 0); solidIndices.push_back(baseVertex + i); @@ -403,7 +417,6 @@ void GeometryCache::buildShapes() { // Line { - Index baseVertex = (Index)(_shapeVertices->getSize() / SHAPE_VERTEX_STRIDE); ShapeData& shapeData = _shapes[Line]; shapeData.setupVertices(_shapeVertices, VertexVector { vec3(-0.5f, 0.0f, 0.0f), vec3(-0.5f, 0.0f, 0.0f), @@ -411,8 +424,8 @@ void GeometryCache::buildShapes() { }); IndexVector wireIndices; // Only two indices - wireIndices.push_back(0 + baseVertex); - wireIndices.push_back(1 + baseVertex); + wireIndices.push_back(0); + wireIndices.push_back(1); shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices); } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 288ab363f0..5a437cf5e9 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -339,14 +339,10 @@ public: void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false); struct ShapeData { - size_t _indexOffset{ 0 }; - size_t _indexCount{ 0 }; - size_t _wireIndexOffset{ 0 }; - size_t _wireIndexCount{ 0 }; - gpu::BufferView _positionView; gpu::BufferView _normalView; - gpu::BufferPointer _indices; + gpu::BufferView _indicesView; + gpu::BufferView _wireIndicesView; void setupVertices(gpu::BufferPointer& vertexBuffer, const geometry::VertexVector& vertices); void setupIndices(gpu::BufferPointer& indexBuffer, const geometry::IndexVector& indices, const geometry::IndexVector& wireIndices); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index dc419c03c3..8b65c9f7ce 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -385,7 +385,7 @@ ItemKey ModelMeshPartPayload::getKey() const { builder.withInvisible(); } - if (model->isLayeredInFront()) { + if (model->isLayeredInFront() || model->isLayeredInHUD()) { builder.withLayered(); } @@ -404,15 +404,15 @@ ItemKey ModelMeshPartPayload::getKey() const { } int ModelMeshPartPayload::getLayer() const { - // MAgic number while we are defining the layering mechanism: - const int LAYER_3D_FRONT = 1; - const int LAYER_3D = 0; ModelPointer model = _model.lock(); - if (model && model->isLayeredInFront()) { - return LAYER_3D_FRONT; - } else { - return LAYER_3D; + if (model) { + if (model->isLayeredInFront()) { + return Item::LAYER_3D_FRONT; + } else if (model->isLayeredInHUD()) { + return Item::LAYER_3D_HUD; + } } + return Item::LAYER_3D; } ShapeKey ModelMeshPartPayload::getShapeKey() const { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 36f12fb1bf..4573b5aa78 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -267,6 +267,11 @@ void Model::updateRenderItems() { }); } +void Model::setRenderItemsNeedUpdate() { + _renderItemsNeedUpdate = true; + emit requestRenderUpdate(); +} + void Model::initJointTransforms() { if (isLoaded()) { glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); @@ -598,6 +603,21 @@ void Model::setLayeredInFront(bool layered, const render::ScenePointer& scene) { } } +void Model::setLayeredInHUD(bool layered, const render::ScenePointer& scene) { + if (_isLayeredInHUD != layered) { + _isLayeredInHUD = layered; + + render::Transaction transaction; + foreach(auto item, _modelMeshRenderItemsMap.keys()) { + transaction.resetItem(item, _modelMeshRenderItemsMap[item]); + } + foreach(auto item, _collisionRenderItemsMap.keys()) { + transaction.resetItem(item, _collisionRenderItemsMap[item]); + } + scene->enqueueTransaction(transaction); + } +} + bool Model::addToScene(const render::ScenePointer& scene, render::Transaction& transaction, render::Item::Status::Getters& statusGetters) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 6d338b4598..0207a4871a 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -84,6 +84,7 @@ public: // new Scene/Engine rendering support void setVisibleInScene(bool newValue, const render::ScenePointer& scene); void setLayeredInFront(bool layered, const render::ScenePointer& scene); + void setLayeredInHUD(bool layered, const render::ScenePointer& scene); bool needsFixupInScene() const; bool needsReload() const { return _needsReload; } @@ -101,9 +102,10 @@ public: bool isVisible() const { return _isVisible; } bool isLayeredInFront() const { return _isLayeredInFront; } + bool isLayeredInHUD() const { return _isLayeredInHUD; } virtual void updateRenderItems(); - void setRenderItemsNeedUpdate() { _renderItemsNeedUpdate = true; } + void setRenderItemsNeedUpdate(); bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; } AABox getRenderableMeshBound() const; const render::ItemIDs& fetchRenderItemIDs() const; @@ -265,6 +267,7 @@ public slots: signals: void setURLFinished(bool success); void setCollisionModelURLFinished(bool success); + void requestRenderUpdate(); protected: @@ -410,6 +413,7 @@ protected: int _renderInfoHasTransparent { false }; bool _isLayeredInFront { false }; + bool _isLayeredInHUD { false }; private: float _loadingPriority { 0.0f }; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 42bfd8add4..d194b94351 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -195,15 +195,20 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZones", zones); } - // Overlays - const auto overlayOpaquesInputs = DrawOverlay3D::Inputs(overlayOpaques, lightingModel).asVarying(); - const auto overlayTransparentsInputs = DrawOverlay3D::Inputs(overlayTransparents, lightingModel).asVarying(); - task.addJob("DrawOverlay3DOpaque", overlayOpaquesInputs, true); - task.addJob("DrawOverlay3DTransparent", overlayTransparentsInputs, false); + // Layered Overlays + const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); + const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); + const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); + const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); - { // Debug the bounds of the rendered Overlay items, still look at the zbuffer - task.addJob("DrawOverlayOpaqueBounds", overlayOpaques); - task.addJob("DrawOverlayTransparentBounds", overlayTransparents); + const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying(); + const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying(); + task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); + task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); + + { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer + task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); + task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); } // Debugging stages @@ -244,6 +249,22 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("Antialiasing", antialiasingInputs); + // Composite the HUD and HUD overlays + task.addJob("HUD"); + + const auto overlaysHUDOpaque = filteredOverlaysOpaque.getN(1); + const auto overlaysHUDTransparent = filteredOverlaysTransparent.getN(1); + + const auto overlayHUDOpaquesInputs = DrawOverlay3D::Inputs(overlaysHUDOpaque, lightingModel).asVarying(); + const auto overlayHUDTransparentsInputs = DrawOverlay3D::Inputs(overlaysHUDTransparent, lightingModel).asVarying(); + task.addJob("DrawOverlayHUDOpaque", overlayHUDOpaquesInputs, true); + task.addJob("DrawOverlayHUDTransparent", overlayHUDTransparentsInputs, false); + + { // Debug the bounds of the rendered Overlay items that are marked drawHUDLayer, still look at the zbuffer + task.addJob("DrawOverlayHUDOpaqueBounds", overlaysHUDOpaque); + task.addJob("DrawOverlayHUDTransparentBounds", overlaysHUDTransparent); + } + task.addJob("ToneAndPostRangeTimer", toneAndPostRangeTimer); // Blit! @@ -418,6 +439,18 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& } } +void CompositeHUD::run(const RenderContextPointer& renderContext) { + assert(renderContext->args); + assert(renderContext->args->_context); + + // Grab the HUD texture + gpu::doInBatch(renderContext->args->_context, [&](gpu::Batch& batch) { + if (renderContext->args->_hudOperator) { + renderContext->args->_hudOperator(batch, renderContext->args->_hudTexture); + } + }); +} + void Blit::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& srcFramebuffer) { assert(renderContext->args); assert(renderContext->args->_context); diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 865849b579..452420589b 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -155,6 +155,14 @@ protected: bool _opaquePass { true }; }; +class CompositeHUD { +public: + using JobModel = render::Job::Model; + + CompositeHUD() {} + void run(const render::RenderContextPointer& renderContext); +}; + class Blit { public: using JobModel = render::Job::ModelI; diff --git a/libraries/render-utils/src/hmd_ui.slf b/libraries/render-utils/src/hmd_ui.slf new file mode 100644 index 0000000000..959e8d733c --- /dev/null +++ b/libraries/render-utils/src/hmd_ui.slf @@ -0,0 +1,37 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// hmd_ui.frag +// fragment shader +// +// Created by Sam Gondelman on 9/28/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 +// + +uniform sampler2D hudTexture; + +struct HUDData { + float alpha; +}; + +layout(std140) uniform hudBuffer { + HUDData hud; +}; + +in vec2 _texCoord0; + +out vec4 fragColor0; + +void main() { + vec4 color = texture(hudTexture, _texCoord0); + color.a *= hud.alpha; + if (color.a <= 0.0) { + discard; + } + + fragColor0 = color; +} \ No newline at end of file diff --git a/libraries/render-utils/src/hmd_ui.slv b/libraries/render-utils/src/hmd_ui.slv new file mode 100644 index 0000000000..d6e02ff4cb --- /dev/null +++ b/libraries/render-utils/src/hmd_ui.slv @@ -0,0 +1,36 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// hmd_ui.vert +// vertex shader +// +// Created by Sam Gondelman on 9/28/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Inputs.slh@> +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +struct HUDData { + float alpha; +}; + +layout(std140) uniform hudBuffer { + HUDData hud; +}; + +out vec2 _texCoord0; + +void main() { + _texCoord0 = inTexCoord0.st; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> +} diff --git a/libraries/render/src/render/Args.h b/libraries/render/src/render/Args.h index 7070a4def5..a76b60ffe6 100644 --- a/libraries/render/src/render/Args.h +++ b/libraries/render/src/render/Args.h @@ -121,6 +121,9 @@ namespace render { RenderDetails _details; render::ScenePointer _scene; int8_t _cameraMode { -1 }; + + std::function _hudOperator; + gpu::TexturePointer _hudTexture; }; } diff --git a/libraries/render/src/render/FilterTask.cpp b/libraries/render/src/render/FilterTask.cpp index e0298c2a44..49a9ada91e 100644 --- a/libraries/render/src/render/FilterTask.cpp +++ b/libraries/render/src/render/FilterTask.cpp @@ -21,19 +21,24 @@ using namespace render; -void FilterLayeredItems::run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) { +void FilterLayeredItems::run(const RenderContextPointer& renderContext, const ItemBounds& inItems, Outputs& outputs) { auto& scene = renderContext->_scene; - // Clear previous values - outItems.clear(); + ItemBounds matchedItems; + ItemBounds nonMatchItems; // For each item, filter it into one bucket for (auto& itemBound : inItems) { auto& item = scene->getItem(itemBound.id); if (item.getLayer() == _keepLayer) { - outItems.emplace_back(itemBound); + matchedItems.emplace_back(itemBound); + } else { + nonMatchItems.emplace_back(itemBound); } } + + outputs.edit0() = matchedItems; + outputs.edit1() = nonMatchItems; } void SliceItems::run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems) { diff --git a/libraries/render/src/render/FilterTask.h b/libraries/render/src/render/FilterTask.h index 1e023a8bb9..a7180b6cde 100644 --- a/libraries/render/src/render/FilterTask.h +++ b/libraries/render/src/render/FilterTask.h @@ -65,15 +65,15 @@ namespace render { // Filter the items belonging to the job's keep layer class FilterLayeredItems { public: - using JobModel = Job::ModelIO; + using Outputs = render::VaryingSet2; + using JobModel = Job::ModelIO; - FilterLayeredItems() {} FilterLayeredItems(int keepLayer) : _keepLayer(keepLayer) {} - int _keepLayer { 0 }; + int _keepLayer; - void run(const RenderContextPointer& renderContext, const ItemBounds& inItems, ItemBounds& outItems); + void run(const RenderContextPointer& renderContext, const ItemBounds& inItems, Outputs& outputs); }; // SliceItems job config defining the slice range diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index f0fbbb1973..036c7d3a99 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -29,6 +29,11 @@ const float Item::Status::Value::CYAN = 180.0f; const float Item::Status::Value::BLUE = 240.0f; const float Item::Status::Value::MAGENTA = 300.0f; +const int Item::LAYER_2D = 0; +const int Item::LAYER_3D = 1; +const int Item::LAYER_3D_FRONT = 2; +const int Item::LAYER_3D_HUD = 3; + void Item::Status::Value::setScale(float scale) { _scale = (std::numeric_limits::max() -1) * 0.5f * (1.0f + std::max(std::min(scale, 1.0f), 0.0f)); } diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 96faf9719e..2b02db81f9 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -356,9 +356,14 @@ public: // Get the bound of the item expressed in world space (or eye space depending on the key.isWorldSpace()) const Bound getBound() const { return _payload->getBound(); } - // Get the layer where the item belongs. 0 by default meaning NOT LAYERED + // Get the layer where the item belongs. int getLayer() const { return _payload->getLayer(); } + static const int LAYER_2D; + static const int LAYER_3D; + static const int LAYER_3D_FRONT; + static const int LAYER_3D_HUD; + // Render call for the item void render(RenderArgs* args) const { _payload->render(args); } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index a2dac7ed43..348a687ae2 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -110,10 +110,13 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) { } message += context->argument(i).toString(); } - qCDebug(scriptengineScript).noquote() << message; // noquote() so that \n is treated as newline if (ScriptEngine *scriptEngine = qobject_cast(engine)) { scriptEngine->print(message); + // prefix the script engine name to help disambiguate messages in the main debug log + qCDebug(scriptengineScript, "[%s] %s", qUtf8Printable(scriptEngine->getFilename()), qUtf8Printable(message)); + } else { + qCDebug(scriptengineScript, "%s", qUtf8Printable(message)); } return QScriptValue(); @@ -468,22 +471,22 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { } void ScriptEngine::scriptErrorMessage(const QString& message) { - qCCritical(scriptengine) << qPrintable(message); + qCCritical(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit errorMessage(message, getFilename()); } void ScriptEngine::scriptWarningMessage(const QString& message) { - qCWarning(scriptengine) << qPrintable(message); + qCWarning(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit warningMessage(message, getFilename()); } void ScriptEngine::scriptInfoMessage(const QString& message) { - qCInfo(scriptengine) << qPrintable(message); + qCInfo(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit infoMessage(message, getFilename()); } void ScriptEngine::scriptPrintedMessage(const QString& message) { - qCDebug(scriptengine) << qPrintable(message); + qCDebug(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message)); emit printedMessage(message, getFilename()); } diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index e40ef814ea..aa67c14c4b 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -177,6 +177,13 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont prefixString.append(QString(" [%1]").arg(_targetName)); } + // for [qml] console.* messages include an abbreviated source filename + if (context.category && context.file && !strcmp("qml", context.category)) { + if (const char* basename = strrchr(context.file, '/')) { + prefixString.append(QString(" [%1]").arg(basename+1)); + } + } + QString logMessage = QString("%1 %2").arg(prefixString, message.split('\n').join('\n' + prefixString + " ")); fprintf(stdout, "%s\n", qPrintable(logMessage)); return logMessage; diff --git a/libraries/shared/src/StatTracker.cpp b/libraries/shared/src/StatTracker.cpp index 0ac9ab0092..adfafd0ea7 100644 --- a/libraries/shared/src/StatTracker.cpp +++ b/libraries/shared/src/StatTracker.cpp @@ -14,15 +14,15 @@ StatTracker::StatTracker() { QVariant StatTracker::getStat(const QString& name) { std::lock_guard lock(_statsLock); - return _stats[name]; + return QVariant::fromValue(_stats[name]); } -void StatTracker::setStat(const QString& name, int value) { +void StatTracker::setStat(const QString& name, int64_t value) { Lock lock(_statsLock); _stats[name] = value; } -void StatTracker::updateStat(const QString& name, int value) { +void StatTracker::updateStat(const QString& name, int64_t value) { Lock lock(_statsLock); auto itr = _stats.find(name); if (_stats.end() == itr) { diff --git a/libraries/shared/src/StatTracker.h b/libraries/shared/src/StatTracker.h index 38afc2c379..4b84bbcb32 100644 --- a/libraries/shared/src/StatTracker.h +++ b/libraries/shared/src/StatTracker.h @@ -24,15 +24,15 @@ class StatTracker : public Dependency { public: StatTracker(); QVariant getStat(const QString& name); - void setStat(const QString& name, int value); - void updateStat(const QString& name, int mod); + void setStat(const QString& name, int64_t value); + void updateStat(const QString& name, int64_t mod); void incrementStat(const QString& name); void decrementStat(const QString& name); private: using Mutex = std::mutex; using Lock = std::lock_guard; Mutex _statsLock; - QHash _stats; + QHash _stats; }; class CounterStat { diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index bea28b2b6f..8ceb378574 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -26,7 +26,6 @@ class FilePersistThread : public GenericQueueThread < QString > { Q_OBJECT public: FilePersistThread(const FileLogger& logger); - signals: void rollingLogFile(QString newFilename); @@ -42,12 +41,14 @@ private: -static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; +static const QString FILENAME_FORMAT = "hifi-log_%1%2.txt"; static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; static const QString LOGS_DIRECTORY = "Logs"; static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; +static QUuid SESSION_ID; + // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. static const qint64 MAX_LOG_SIZE = 512 * 1024; @@ -62,7 +63,13 @@ QString getLogRollerFilename() { QString result = FileUtils::standardPath(LOGS_DIRECTORY); QHostAddress clientAddress = getGuessedLocalAddress(); QDateTime now = QDateTime::currentDateTime(); - result.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT))); + QString fileSessionID; + + if (!SESSION_ID.isNull()) { + fileSessionID = "_" + SESSION_ID.toString().replace("{", "").replace("}", ""); + } + + result.append(QString(FILENAME_FORMAT).arg(now.toString(DATETIME_FORMAT), fileSessionID)); return result; } @@ -142,6 +149,12 @@ FileLogger::~FileLogger() { _persistThreadInstance->terminate(); } +void FileLogger::setSessionID(const QUuid& message) { + // This is for the output of log files. Once the application is first started, + // this function runs and grabs the AccountManager Session ID and saves it here. + SESSION_ID = message; + } + void FileLogger::addMessage(const QString& message) { _persistThreadInstance->queueItem(message); emit logReceived(message); diff --git a/libraries/shared/src/shared/FileLogger.h b/libraries/shared/src/shared/FileLogger.h index 15d211afe8..d9d7651147 100644 --- a/libraries/shared/src/shared/FileLogger.h +++ b/libraries/shared/src/shared/FileLogger.h @@ -26,6 +26,7 @@ public: QString getFilename() const { return _fileName; } virtual void addMessage(const QString&) override; + virtual void setSessionID(const QUuid&); virtual QString getLogData() override; virtual void locateLog() override; virtual void sync() override; diff --git a/scripts/developer/utilities/lib/configprop/ConfigSlider.qml b/scripts/developer/utilities/lib/configprop/ConfigSlider.qml new file mode 100644 index 0000000000..f82b381a2d --- /dev/null +++ b/scripts/developer/utilities/lib/configprop/ConfigSlider.qml @@ -0,0 +1,75 @@ +// +// ConfigSlider.qml +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 +import QtQuick.Controls 1.4 as Original +import QtQuick.Controls.Styles 1.4 + +import "../hifi-qml/styles-uit" +import "../hifi-qml/controls-uit" as HifiControls + + +Item { + HifiConstants { id: luci } + id: root + + anchors.left: parent.left + anchors.right: parent.right + height: 24 + property bool integral: false + property var config + property string property + property alias label: labelControl.text + property alias min: sliderControl.minimumValue + property alias max: sliderControl.maximumValue + + Component.onCompleted: { + // Binding favors qml value, so set it first + sliderControl.value = root.config[root.property]; + bindingControl.when = true; + } + + HifiControls.Label { + id: labelControl + text: root.label + enabled: true + anchors.left: root.left + anchors.right: root.horizontalCenter + anchors.verticalCenter: root.verticalCenter + //anchors.topMargin: 7 + } + + HifiControls.Label { + id: labelValue + text: sliderControl.value.toFixed(root.integral ? 0 : 2) + anchors.right: root.right + anchors.bottom: root.bottom + anchors.bottomMargin: 0 + } + + Binding { + id: bindingControl + target: root.config + property: root.property + value: sliderControl.value + when: false + } + + HifiControls.Slider { + id: sliderControl + stepSize: root.integral ? 1.0 : 0.0 + //height: 20 + anchors.left: root.horizontalCenter + anchors.right: root.right + anchors.rightMargin: 0 + anchors.top: root.top + anchors.topMargin: 0 + } +} diff --git a/scripts/developer/utilities/lib/fonts/AnonymousPro-Regular.ttf b/scripts/developer/utilities/lib/fonts/AnonymousPro-Regular.ttf new file mode 100644 index 0000000000..06aafc0673 Binary files /dev/null and b/scripts/developer/utilities/lib/fonts/AnonymousPro-Regular.ttf differ diff --git a/scripts/developer/utilities/lib/fonts/AnonymousPro.license b/scripts/developer/utilities/lib/fonts/AnonymousPro.license new file mode 100644 index 0000000000..5e8b7385e7 --- /dev/null +++ b/scripts/developer/utilities/lib/fonts/AnonymousPro.license @@ -0,0 +1,93 @@ +Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com), +with Reserved Font Name Anonymous Pro. +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/scripts/developer/utilities/lib/fonts/FiraSans-Regular.ttf b/scripts/developer/utilities/lib/fonts/FiraSans-Regular.ttf new file mode 100644 index 0000000000..d9fdc0e922 Binary files /dev/null and b/scripts/developer/utilities/lib/fonts/FiraSans-Regular.ttf differ diff --git a/scripts/developer/utilities/lib/fonts/FiraSans-SemiBold.ttf b/scripts/developer/utilities/lib/fonts/FiraSans-SemiBold.ttf new file mode 100644 index 0000000000..821a43d7fd Binary files /dev/null and b/scripts/developer/utilities/lib/fonts/FiraSans-SemiBold.ttf differ diff --git a/scripts/developer/utilities/lib/fonts/FiraSans.license b/scripts/developer/utilities/lib/fonts/FiraSans.license new file mode 100644 index 0000000000..d444ea92b6 --- /dev/null +++ b/scripts/developer/utilities/lib/fonts/FiraSans.license @@ -0,0 +1,94 @@ +Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. +with Reserved Font Name < Fira >, + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/scripts/developer/utilities/lib/fonts/Raleway-Bold.ttf b/scripts/developer/utilities/lib/fonts/Raleway-Bold.ttf new file mode 100644 index 0000000000..38c099cc85 Binary files /dev/null and b/scripts/developer/utilities/lib/fonts/Raleway-Bold.ttf differ diff --git a/scripts/developer/utilities/lib/fonts/Raleway-Light.ttf b/scripts/developer/utilities/lib/fonts/Raleway-Light.ttf new file mode 100644 index 0000000000..91aa0c701f Binary files /dev/null and b/scripts/developer/utilities/lib/fonts/Raleway-Light.ttf differ diff --git a/scripts/developer/utilities/lib/fonts/Raleway-Regular.ttf b/scripts/developer/utilities/lib/fonts/Raleway-Regular.ttf new file mode 100644 index 0000000000..e570a2d5c3 Binary files /dev/null and b/scripts/developer/utilities/lib/fonts/Raleway-Regular.ttf differ diff --git a/scripts/developer/utilities/lib/fonts/Raleway-SemiBold.ttf b/scripts/developer/utilities/lib/fonts/Raleway-SemiBold.ttf new file mode 100644 index 0000000000..ed0a8b9941 Binary files /dev/null and b/scripts/developer/utilities/lib/fonts/Raleway-SemiBold.ttf differ diff --git a/scripts/developer/utilities/lib/fonts/Raleway.license b/scripts/developer/utilities/lib/fonts/Raleway.license new file mode 100644 index 0000000000..1c9779ddcd --- /dev/null +++ b/scripts/developer/utilities/lib/fonts/Raleway.license @@ -0,0 +1,94 @@ +Copyright (c) 2010, Matt McInerney (matt@pixelspread.com), +Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari@gmail.com), +Copyright (c) 2011, Rodrigo Fuenzalida (www.rfuenzalida.com|hello@rfuenzalida.com), with Reserved Font Name Raleway +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/scripts/developer/utilities/lib/fonts/fontawesome-webfont.ttf b/scripts/developer/utilities/lib/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000..d7994e1308 Binary files /dev/null and b/scripts/developer/utilities/lib/fonts/fontawesome-webfont.ttf differ diff --git a/scripts/developer/utilities/lib/fonts/hifi-glyphs.ttf b/scripts/developer/utilities/lib/fonts/hifi-glyphs.ttf new file mode 100644 index 0000000000..3db48602b1 Binary files /dev/null and b/scripts/developer/utilities/lib/fonts/hifi-glyphs.ttf differ diff --git a/scripts/developer/utilities/lib/fonts/hifi-glyphs/icons-reference.html b/scripts/developer/utilities/lib/fonts/hifi-glyphs/icons-reference.html new file mode 100644 index 0000000000..99e826e0b9 --- /dev/null +++ b/scripts/developer/utilities/lib/fonts/hifi-glyphs/icons-reference.html @@ -0,0 +1,1056 @@ + + + + + + + Font Reference - HiFi Glyphs + + + + + +
+

HiFi Glyphs

+

This font was created inHigh Fidelity

+

CSS mapping

+
    +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • + + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • + + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
+

Character mapping

+
    +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
+
+ + + diff --git a/scripts/developer/utilities/lib/fonts/hifi-glyphs/styles.css b/scripts/developer/utilities/lib/fonts/hifi-glyphs/styles.css new file mode 100644 index 0000000000..66277740fc --- /dev/null +++ b/scripts/developer/utilities/lib/fonts/hifi-glyphs/styles.css @@ -0,0 +1,421 @@ +@charset "UTF-8"; + +@font-face { + font-family: "hifi-glyphs"; + src:url("fonts/hifi-glyphs.eot"); + src:url("fonts/hifi-glyphs.eot?#iefix") format("embedded-opentype"), + url("fonts/hifi-glyphs.woff") format("woff"), + url("fonts/hifi-glyphs.ttf") format("truetype"), + url("fonts/hifi-glyphs.svg#hifi-glyphs") format("svg"); + font-weight: normal; + font-style: normal; + +} + +[data-icon]:before { + font-family: "hifi-glyphs" !important; + content: attr(data-icon); + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +[class^="icon-"]:before, +[class*=" icon-"]:before { + font-family: "hifi-glyphs" !important; + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-hmd:before { + content: "\62"; +} +.icon-2d-screen:before { + content: "\63"; +} +.icon-keyboard:before { + content: "\64"; +} +.icon-hand-controllers:before { + content: "\65"; +} +.icon-headphones-mic:before { + content: "\66"; +} +.icon-gamepad:before { + content: "\67"; +} +.icon-headphones:before { + content: "\68"; +} +.icon-mic:before { + content: "\69"; +} +.icon-upload:before { + content: "\6a"; +} +.icon-script:before { + content: "\6b"; +} +.icon-text:before { + content: "\6c"; +} +.icon-cube:before { + content: "\6d"; +} +.icon-sphere:before { + content: "\6e"; +} +.icon-zone:before { + content: "\6f"; +} +.icon-light:before { + content: "\70"; +} +.icon-web:before { + content: "\71"; +} +.icon-web-2:before { + content: "\72"; +} +.icon-edit:before { + content: "\73"; +} +.icon-market:before { + content: "\74"; +} +.icon-directory:before { + content: "\75"; +} +.icon-menu:before { + content: "\76"; +} +.icon-close:before { + content: "\77"; +} +.icon-close-inverted:before { + content: "\78"; +} +.icon-pin:before { + content: "\79"; +} +.icon-pin-inverted:before { + content: "\7a"; +} +.icon-resize-handle:before { + content: "\41"; +} +.icon-diclosure-expand:before { + content: "\42"; +} +.icon-reload-small:before { + content: "\61"; +} +.icon-close-small:before { + content: "\43"; +} +.icon-backward:before { + content: "\45"; +} +.icon-reload:before { + content: "\46"; +} +.icon-minimize:before { + content: "\49"; +} +.icon-maximize:before { + content: "\4a"; +} +.icon-maximize-inverted:before { + content: "\4b"; +} +.icon-disclosure-button-expand:before { + content: "\4c"; +} +.icon-disclosure-button-collapse:before { + content: "\4d"; +} +.icon-script-stop:before { + content: "\4e"; +} +.icon-script-reload:before { + content: "\4f"; +} +.icon-script-run:before { + content: "\50"; +} +.icon-script-new:before { + content: "\51"; +} +.icon-hifi-forum:before { + content: "\32"; +} +.icon-hifi-logo-small:before { + content: "\53"; +} +.icon-avatar-1:before { + content: "\54"; +} +.icon-placemark:before { + content: "\55"; +} +.icon-box:before { + content: "\56"; +} +.icon-community:before { + content: "\30"; +} +.icon-grab-handle:before { + content: "\58"; +} +.icon-search:before { + content: "\59"; +} +.icon-disclosure-collapse:before { + content: "\5a"; +} +.icon-script-upload:before { + content: "\52"; +} +.icon-code:before { + content: "\57"; +} +.icon-avatar:before { + content: "\3c"; +} +.icon-arrows-h:before { + content: "\3a"; +} +.icon-arrows-v:before { + content: "\3b"; +} +.icon-arrows:before { + content: "\60"; +} +.icon-compress:before { + content: "\21"; +} +.icon-expand:before { + content: "\22"; +} +.icon-placemark-1:before { + content: "\23"; +} +.icon-circle:before { + content: "\24"; +} +.icon-hand-pointer:before { + content: "\39"; +} +.icon-plus-square-o:before { + content: "\25"; +} +.icon-square:before { + content: "\27"; +} +.icon-align-center:before { + content: "\38"; +} +.icon-align-justify:before { + content: "\29"; +} +.icon-align-left:before { + content: "\2a"; +} +.icon-align-right:before { + content: "\5e"; +} +.icon-bars:before { + content: "\37"; +} +.icon-circle-slash:before { + content: "\2c"; +} +.icon-sync:before { + content: "\28"; +} +.icon-key:before { + content: "\2d"; +} +.icon-link:before { + content: "\2e"; +} +.icon-location:before { + content: "\2f"; +} +.icon-carat-r:before { + content: "\33"; +} +.icon-carat-l:before { + content: "\34"; +} +.icon-folder-lg:before { + content: "\3e"; +} +.icon-folder-sm:before { + content: "\3f"; +} +.icon-level-up:before { + content: "\31"; +} +.icon-info:before { + content: "\5b"; +} +.icon-question:before { + content: "\5d"; +} +.icon-alert:before { + content: "\2b"; +} +.icon-home:before { + content: "\5f"; +} +.icon-error:before { + content: "\3d"; +} +.icon-settings:before { + content: "\40"; +} +.icon-trash:before { + content: "\7b"; +} +.icon-object-group:before { + content: "\e000"; +} +.icon-cm:before { + content: "\7d"; +} +.icon-msvg:before { + content: "\7e"; +} +.icon-deg:before { + content: "\5c"; +} +.icon-px:before { + content: "\7c"; +} +.icon-m-sq:before { + content: "\e001"; +} +.icon-m-cubed:before { + content: "\e002"; +} +.icon-acceleration:before { + content: "\e003"; +} +.icon-particles:before { + content: "\e004"; +} +.icon-voxels:before { + content: "\e005"; +} +.icon-lock:before { + content: "\e006"; +} +.icon-visible:before { + content: "\e007"; +} +.icon-model:before { + content: "\e008"; +} +.icon-forward:before { + content: "\44"; +} +.icon-avatar-2:before { + content: "\e009"; +} +.icon-arrow-dn:before { + content: "\35"; +} +.icon-arrow-up:before { + content: "\36"; +} +.icon-time:before { + content: "\e00a"; +} +.icon-transparency:before { + content: "\e00b"; +} +.icon-unmuted:before { + content: "\47"; +} +.icon-user:before { + content: "\e00c"; +} +.icon-edit-pencil:before { + content: "\e00d"; +} +.icon-muted:before { + content: "\48"; +} +.icon-vol-0:before { + content: "\e00e"; +} +.icon-vol-1:before { + content: "\e00f"; +} +.icon-vol-2:before { + content: "\e010"; +} +.icon-vol-3:before { + content: "\e011"; +} +.icon-vol-4:before { + content: "\e012"; +} +.icon-vol-x-0:before { + content: "\e013"; +} +.icon-vol-x-1:before { + content: "\e014"; +} +.icon-vol-x-2:before { + content: "\e015"; +} +.icon-vol-x-3:before { + content: "\e016"; +} +.icon-vol-x-4:before { + content: "\e017"; +} +.icon-share-ext:before { + content: "\e018"; +} +.icon-ellipsis:before { + content: "\e019"; +} +.icon-check:before { + content: "\e01a"; +} +.icon-sliders:before { + content: "\26"; +} +.icon-polyline:before { + content: "\e01b"; +} +.icon-source:before { + content: "\e01c"; +} +.icon-playback-play:before { + content: "\e01d"; +} +.icon-stop-square:before { + content: "\e01e"; +} +.icon-avatar-t-pose:before { + content: "\e01f"; +} +.icon-check-2-01:before { + content: "\e020"; +} diff --git a/scripts/developer/utilities/lib/controls-uit/Button.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Button.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/Button.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/Button.qml diff --git a/scripts/developer/utilities/lib/controls-uit/CheckBox.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/CheckBox.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/CheckBox.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/CheckBox.qml diff --git a/scripts/developer/utilities/lib/controls-uit/ComboBox.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/ComboBox.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/ComboBox.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/ComboBox.qml diff --git a/scripts/developer/utilities/lib/controls-uit/ConfigSlider.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/ConfigSlider.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/ConfigSlider.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/ConfigSlider.qml diff --git a/scripts/developer/utilities/lib/controls-uit/ContentSection.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/ContentSection.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/ContentSection.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/ContentSection.qml diff --git a/scripts/developer/utilities/lib/controls-uit/GlyphButton.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/GlyphButton.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/GlyphButton.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/GlyphButton.qml diff --git a/scripts/developer/utilities/lib/controls-uit/HorizontalRule.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalRule.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/HorizontalRule.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalRule.qml diff --git a/scripts/developer/utilities/lib/controls-uit/HorizontalSpacer.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalSpacer.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/HorizontalSpacer.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/HorizontalSpacer.qml diff --git a/scripts/developer/utilities/lib/controls-uit/Label.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Label.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/Label.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/Label.qml diff --git a/scripts/developer/utilities/lib/controls-uit/QueuedButton.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/QueuedButton.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/QueuedButton.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/QueuedButton.qml diff --git a/scripts/developer/utilities/lib/controls-uit/RadioButton.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/RadioButton.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/RadioButton.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/RadioButton.qml diff --git a/scripts/developer/utilities/lib/controls-uit/Separator.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Separator.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/Separator.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/Separator.qml diff --git a/scripts/developer/utilities/lib/controls-uit/Slider.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Slider.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/Slider.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/Slider.qml diff --git a/scripts/developer/utilities/lib/controls-uit/SpinBox.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/SpinBox.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/SpinBox.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/SpinBox.qml diff --git a/scripts/developer/utilities/lib/controls-uit/Switch.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Switch.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/Switch.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/Switch.qml diff --git a/scripts/developer/utilities/lib/controls-uit/Table.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/Table.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/Table.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/Table.qml diff --git a/scripts/developer/utilities/lib/controls-uit/VerticalSpacer.qml b/scripts/developer/utilities/lib/hifi-qml/controls-uit/VerticalSpacer.qml similarity index 100% rename from scripts/developer/utilities/lib/controls-uit/VerticalSpacer.qml rename to scripts/developer/utilities/lib/hifi-qml/controls-uit/VerticalSpacer.qml diff --git a/scripts/developer/utilities/lib/styles-uit/AnonymousProRegular.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/AnonymousProRegular.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/AnonymousProRegular.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/AnonymousProRegular.qml diff --git a/scripts/developer/utilities/lib/styles-uit/ButtonLabel.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/ButtonLabel.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/ButtonLabel.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/ButtonLabel.qml diff --git a/scripts/developer/utilities/lib/styles-uit/FiraSansRegular.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansRegular.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/FiraSansRegular.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansRegular.qml diff --git a/scripts/developer/utilities/lib/styles-uit/FiraSansSemiBold.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/FiraSansSemiBold.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/FiraSansSemiBold.qml diff --git a/scripts/developer/utilities/lib/styles-uit/HiFiGlyphs.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/HiFiGlyphs.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/HiFiGlyphs.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/HiFiGlyphs.qml diff --git a/scripts/developer/utilities/lib/styles-uit/HifiConstants.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/HifiConstants.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/HifiConstants.qml diff --git a/scripts/developer/utilities/lib/styles-uit/IconButton.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/IconButton.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/IconButton.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/IconButton.qml diff --git a/scripts/developer/utilities/lib/styles-uit/InfoItem.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/InfoItem.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/InfoItem.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/InfoItem.qml diff --git a/scripts/developer/utilities/lib/styles-uit/InputLabel.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/InputLabel.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/InputLabel.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/InputLabel.qml diff --git a/scripts/developer/utilities/lib/styles-uit/ListItem.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/ListItem.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/ListItem.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/ListItem.qml diff --git a/scripts/developer/utilities/lib/styles-uit/Logs.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/Logs.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/Logs.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/Logs.qml diff --git a/scripts/developer/utilities/lib/styles-uit/OverlayTitle.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/OverlayTitle.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/OverlayTitle.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/OverlayTitle.qml diff --git a/scripts/developer/utilities/lib/styles-uit/RalewayBold.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewayBold.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/RalewayBold.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewayBold.qml diff --git a/scripts/developer/utilities/lib/styles-uit/RalewayLight.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewayLight.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/RalewayLight.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewayLight.qml diff --git a/scripts/developer/utilities/lib/styles-uit/RalewayRegular.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewayRegular.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/RalewayRegular.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewayRegular.qml diff --git a/scripts/developer/utilities/lib/styles-uit/RalewaySemiBold.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewaySemiBold.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/RalewaySemiBold.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/RalewaySemiBold.qml diff --git a/scripts/developer/utilities/lib/styles-uit/SectionName.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/SectionName.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/SectionName.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/SectionName.qml diff --git a/scripts/developer/utilities/lib/styles-uit/Separator.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/Separator.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/Separator.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/Separator.qml diff --git a/scripts/developer/utilities/lib/styles-uit/ShortcutText.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/ShortcutText.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/ShortcutText.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/ShortcutText.qml diff --git a/scripts/developer/utilities/lib/styles-uit/TabName.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/TabName.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/TabName.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/TabName.qml diff --git a/scripts/developer/utilities/lib/styles-uit/TextFieldInput.qml b/scripts/developer/utilities/lib/hifi-qml/styles-uit/TextFieldInput.qml similarity index 100% rename from scripts/developer/utilities/lib/styles-uit/TextFieldInput.qml rename to scripts/developer/utilities/lib/hifi-qml/styles-uit/TextFieldInput.qml diff --git a/scripts/developer/utilities/lib/styles-uit/HifiConstants.qmlc b/scripts/developer/utilities/lib/styles-uit/HifiConstants.qmlc deleted file mode 100644 index f63d5ef412..0000000000 Binary files a/scripts/developer/utilities/lib/styles-uit/HifiConstants.qmlc and /dev/null differ diff --git a/scripts/developer/utilities/render/TestQML/antialiasing.js b/scripts/developer/utilities/render/TestQML/antialiasing.js deleted file mode 100644 index a3414218a2..0000000000 --- a/scripts/developer/utilities/render/TestQML/antialiasing.js +++ /dev/null @@ -1,99 +0,0 @@ -"use strict"; - -// -// gemstoneMagicMaker.js -// tablet-sample-app -// -// Created by Faye Li on Feb 6 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() { - var TABLET_BUTTON_NAME = "TAA"; - var QMLAPP_URL = Script.resolvePath("../antialiasing.qml"); - - - var onLuciScreen = false; - - function onClicked() { - if (onLuciScreen) { - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(QMLAPP_URL); - } - } - - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var button = tablet.addButton({ - text: TABLET_BUTTON_NAME, - sortOrder: 1 - }); - - var hasEventBridge = false; - - function wireEventBridge(on) { - if (!tablet) { - print("Warning in wireEventBridge(): 'tablet' undefined!"); - return; - } - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } - } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; - } - } - } - - function onScreenChanged(type, url) { - if (url === QMLAPP_URL) { - onLuciScreen = true; - } else { - onLuciScreen = false; - } - - button.editProperties({isActive: onLuciScreen}); - wireEventBridge(onLuciScreen); - } - - function fromQml(message) { - } - - button.clicked.connect(onClicked); - tablet.screenChanged.connect(onScreenChanged); - - var moveDebugCursor = false; - Controller.mousePressEvent.connect(function (e) { - if (e.isMiddleButton) { - moveDebugCursor = true; - setDebugCursor(e.x, e.y); - } - }); - Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; }); - Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); }); - - - Script.scriptEnding.connect(function () { - if (onLuciScreen) { - tablet.gotoHomeScreen(); - } - button.clicked.disconnect(onClicked); - tablet.screenChanged.disconnect(onScreenChanged); - tablet.removeButton(button); - }); - - function setDebugCursor(x, y) { - nx = ((x + 0.5) / Window.innerWidth); - ny = 1.0 - ((y + 0.5) / (Window.innerHeight)); - - Render.getConfig("RenderMainView").getConfig("Antialiasing").debugCursorTexcoord = { x: nx, y: ny }; - } - -}()); \ No newline at end of file diff --git a/scripts/developer/utilities/render/antialiasing.js b/scripts/developer/utilities/render/antialiasing.js index d862488043..b6c146caac 100644 --- a/scripts/developer/utilities/render/antialiasing.js +++ b/scripts/developer/utilities/render/antialiasing.js @@ -1,39 +1,99 @@ +"use strict"; + // -// antialiasing.js +// gemstoneMagicMaker.js +// tablet-sample-app // -// Created by Sam Gateau on 8/14/2017 +// Created by Faye Li on Feb 6 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 +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// Set up the qml ui -var qml = Script.resolvePath('antialiasing.qml'); -var window = new OverlayWindow({ - title: 'Antialiasing', - source: qml, - width: 400, height:400, -}); -window.setPosition(Window.innerWidth - 420, 50); -window.closed.connect(function() { Script.stop(); }); +(function() { + var TABLET_BUTTON_NAME = "TAA"; + var QMLAPP_URL = Script.resolvePath("./Antialiasing.qml"); + + var onLuciScreen = false; - -var moveDebugCursor = false; -Controller.mousePressEvent.connect(function (e) { - if (e.isMiddleButton) { - moveDebugCursor = true; - setDebugCursor(e.x, e.y); + function onClicked() { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } } -}); -Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; }); -Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); }); + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + sortOrder: 1 + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === QMLAPP_URL) { + onLuciScreen = true; + } else { + onLuciScreen = false; + } + + button.editProperties({isActive: onLuciScreen}); + wireEventBridge(onLuciScreen); + } + + function fromQml(message) { + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + var moveDebugCursor = false; + Controller.mousePressEvent.connect(function (e) { + if (e.isMiddleButton) { + moveDebugCursor = true; + setDebugCursor(e.x, e.y); + } + }); + Controller.mouseReleaseEvent.connect(function() { moveDebugCursor = false; }); + Controller.mouseMoveEvent.connect(function (e) { if (moveDebugCursor) setDebugCursor(e.x, e.y); }); -function setDebugCursor(x, y) { - nx = (x / Window.innerWidth); - ny = 1.0 - ((y) / (Window.innerHeight - 32)); + Script.scriptEnding.connect(function () { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); - Render.getConfig("RenderMainView").getConfig("Antialiasing").debugCursorTexcoord = { x: nx, y: ny }; -} \ No newline at end of file + function setDebugCursor(x, y) { + nx = ((x + 0.5) / Window.innerWidth); + ny = 1.0 - ((y + 0.5) / (Window.innerHeight)); + + Render.getConfig("RenderMainView").getConfig("Antialiasing").debugCursorTexcoord = { x: nx, y: ny }; + } + +}()); \ No newline at end of file diff --git a/scripts/developer/utilities/render/antialiasing.qml b/scripts/developer/utilities/render/antialiasing.qml index c194b0ace4..1c58cf8437 100644 --- a/scripts/developer/utilities/render/antialiasing.qml +++ b/scripts/developer/utilities/render/antialiasing.qml @@ -19,6 +19,7 @@ import "../lib/controls-uit" as HifiControls import "configSlider" import "../lib/plotperf" +Item { Rectangle { id: root; @@ -204,3 +205,4 @@ Rectangle { } } } +} diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index a369301570..12552bd982 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -7,206 +7,267 @@ // 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 2.7 import QtQuick.Controls 1.4 -import "configSlider" +import QtQuick.Layouts 1.3 -Column { - id: root - spacing: 8 - property var mainViewTask: Render.getConfig("RenderMainView") +import "../lib/hifi-qml/styles-uit" +import "../lib/hifi-qml/controls-uit" as HifiControls +import "../lib/configprop" +import "." +Rectangle { + HifiConstants { id: hifi;} + id: render; + anchors.margins: hifi.dimensions.contentMargin.x + color: hifi.colors.baseGray; + property var mainViewTask: Render.getConfig("RenderMainView") + Row { - spacing: 8 - Column { - spacing: 10 - Repeater { - model: [ - "Unlit:LightingModel:enableUnlit", - "Emissive:LightingModel:enableEmissive", - "Lightmap:LightingModel:enableLightmap", - "Background:LightingModel:enableBackground", - "ssao:AmbientOcclusion:enabled", - "Textures:LightingModel:enableMaterialTexturing" - ] - CheckBox { - text: modelData.split(":")[0] - checked: mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] - onCheckedChanged: { mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } - } - } - } - - - Column { - spacing: 10 - Repeater { - model: [ - "Obscurance:LightingModel:enableObscurance", - "Scattering:LightingModel:enableScattering", - "Diffuse:LightingModel:enableDiffuse", - "Specular:LightingModel:enableSpecular", - "Albedo:LightingModel:enableAlbedo", - "Wireframe:LightingModel:enableWireframe" - ] - CheckBox { - text: modelData.split(":")[0] - checked: mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] - onCheckedChanged: { mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } - } - } - } - - Column { - spacing: 10 - Repeater { - model: [ - "Ambient:LightingModel:enableAmbientLight", - "Directional:LightingModel:enableDirectionalLight", - "Point:LightingModel:enablePointLight", - "Spot:LightingModel:enableSpotLight", - "Light Contour:LightingModel:showLightContour", - "Zone Stack:DrawZoneStack:enabled", - "Shadow:RenderShadowTask:enabled" - ] - CheckBox { - text: modelData.split(":")[0] - checked: mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] - onCheckedChanged: { mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } - } - } - } - } + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 10 Column { - spacing: 10 - Repeater { - model: [ "Tone Mapping Exposure:ToneMapping:exposure:5.0:-5.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: mainViewTask.getConfig(modelData.split(":")[1]) - property: modelData.split(":")[2] - max: modelData.split(":")[3] - min: modelData.split(":")[4] + padding: 10 + spacing: 5 + + anchors.left: parent.left + anchors.right: parent.right + // padding: hifi.dimensions.contentMargin.x + + ConfigSlider { + label: qsTr("ToneMapping") + integral: false + config: render.mainViewTask.getConfig("ToneMapping") + property: "exposure" + max: 2 + min: 0 + } + + Row { + + spacing: 20 + padding: 10 + Column { + spacing: 10 + Repeater { + model: [ + "Unlit:LightingModel:enableUnlit", + "Emissive:LightingModel:enableEmissive", + "Lightmap:LightingModel:enableLightmap", + "Background:LightingModel:enableBackground", + "ssao:AmbientOcclusion:enabled", + "Textures:LightingModel:enableMaterialTexturing" + ] + HifiControls.CheckBox { + boxSize: 20 + text: modelData.split(":")[0] + checked: render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] + onCheckedChanged: { render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } + } + } + } + + + Column { + spacing: 10 + Repeater { + model: [ + "Obscurance:LightingModel:enableObscurance", + "Scattering:LightingModel:enableScattering", + "Diffuse:LightingModel:enableDiffuse", + "Specular:LightingModel:enableSpecular", + "Albedo:LightingModel:enableAlbedo", + "Wireframe:LightingModel:enableWireframe" + ] + HifiControls.CheckBox { + boxSize: 20 + text: modelData.split(":")[0] + checked: render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] + onCheckedChanged: { render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } + } + } + } + + Column { + spacing: 10 + Repeater { + model: [ + "Ambient:LightingModel:enableAmbientLight", + "Directional:LightingModel:enableDirectionalLight", + "Point:LightingModel:enablePointLight", + "Spot:LightingModel:enableSpotLight", + "Light Contour:LightingModel:showLightContour", + "Zone Stack:DrawZoneStack:enabled", + "Shadow:RenderShadowTask:enabled" + ] + HifiControls.CheckBox { + boxSize: 20 + text: modelData.split(":")[0] + checked: render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] + onCheckedChanged: { render.mainViewTask.getConfig(modelData.split(":")[1])[modelData.split(":")[2]] = checked } + } + } } } + Separator {} + Column { + spacing: 10 + Repeater { + model: [ "Tone Mapping Exposure:ToneMapping:exposure:5.0:-5.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: render.mainViewTask.getConfig(modelData.split(":")[1]) + property: modelData.split(":")[2] + max: modelData.split(":")[3] + min: modelData.split(":")[4] + } + } + Row { + HifiControls.Label { + text: "Tone Mapping Curve" + anchors.left: root.left + } + + HifiControls.ComboBox { + anchors.right: root.right + currentIndex: 1 + model: ListModel { + id: cbItems + ListElement { text: "RGB"; color: "Yellow" } + ListElement { text: "SRGB"; color: "Green" } + ListElement { text: "Reinhard"; color: "Yellow" } + ListElement { text: "Filmic"; color: "White" } + } + width: 200 + onCurrentIndexChanged: { render.mainViewTask.getConfig("ToneMapping")["curve"] = currentIndex } + } + } + } Row { - Label { - text: "Tone Mapping Curve" + id: framebuffer + spacing: 10 + height: 24 + + HifiControls.Label { + text: "Debug Framebuffer" + height: 24 anchors.left: root.left } + + property var config: render.mainViewTask.getConfig("DebugDeferredBuffer") - ComboBox { + function setDebugMode(mode) { + framebuffer.config.enabled = (mode != 0); + framebuffer.config.mode = mode; + } + + HifiControls.ComboBox { + height: 24 anchors.right: root.right - currentIndex: 1 + currentIndex: 0 model: ListModel { - id: cbItems - ListElement { text: "RGB"; color: "Yellow" } - ListElement { text: "SRGB"; color: "Green" } - ListElement { text: "Reinhard"; color: "Yellow" } - ListElement { text: "Filmic"; color: "White" } + id: cbItemsFramebuffer + ListElement { text: "Off"; color: "Yellow" } + ListElement { text: "Depth"; color: "Green" } + ListElement { text: "Albedo"; color: "Yellow" } + ListElement { text: "Normal"; color: "White" } + ListElement { text: "Roughness"; color: "White" } + ListElement { text: "Metallic"; color: "White" } + ListElement { text: "Emissive"; color: "White" } + ListElement { text: "Unlit"; color: "White" } + ListElement { text: "Occlusion"; color: "White" } + ListElement { text: "Lightmap"; color: "White" } + ListElement { text: "Scattering"; color: "White" } + ListElement { text: "Lighting"; color: "White" } + ListElement { text: "Shadow"; color: "White" } + ListElement { text: "Linear Depth"; color: "White" } + ListElement { text: "Half Linear Depth"; color: "White" } + ListElement { text: "Half Normal"; color: "White" } + ListElement { text: "Mid Curvature"; color: "White" } + ListElement { text: "Mid Normal"; color: "White" } + ListElement { text: "Low Curvature"; color: "White" } + ListElement { text: "Low Normal"; color: "White" } + ListElement { text: "Curvature Occlusion"; color: "White" } + ListElement { text: "Debug Scattering"; color: "White" } + ListElement { text: "Ambient Occlusion"; color: "White" } + ListElement { text: "Ambient Occlusion Blurred"; color: "White" } + ListElement { text: "Custom"; color: "White" } } width: 200 - onCurrentIndexChanged: { mainViewTask.getConfig("ToneMapping")["curve"] = currentIndex } + onCurrentIndexChanged: { framebuffer.setDebugMode(currentIndex) } } } - } - Row { - id: framebuffer - spacing: 10 - Label { - text: "Debug Framebuffer" - anchors.left: root.left - } - - property var config: mainViewTask.getConfig("DebugDeferredBuffer") + Separator {} + Row { + spacing: 10 + Column { + spacing: 10 - function setDebugMode(mode) { - framebuffer.config.enabled = (mode != 0); - framebuffer.config.mode = mode; - } + HifiControls.CheckBox { + boxSize: 20 + text: "Opaques" + checked: render.mainViewTask.getConfig("DrawOpaqueBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOpaqueBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Transparents" + checked: render.mainViewTask.getConfig("DrawTransparentBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawTransparentBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Opaques in Front" + checked: render.mainViewTask.getConfig("DrawOverlayInFrontOpaqueBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayInFrontOpaqueBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Transparents in Front" + checked: render.mainViewTask.getConfig("DrawOverlayInFrontTransparentBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayInFrontTransparentBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Opaques in HUD" + checked: render.mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDOpaqueBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Transparents in HUD" + checked: render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawOverlayHUDTransparentBounds")["enabled"] = checked } + } - ComboBox { - anchors.right: root.right - currentIndex: 0 - model: ListModel { - id: cbItemsFramebuffer - ListElement { text: "Off"; color: "Yellow" } - ListElement { text: "Depth"; color: "Green" } - ListElement { text: "Albedo"; color: "Yellow" } - ListElement { text: "Normal"; color: "White" } - ListElement { text: "Roughness"; color: "White" } - ListElement { text: "Metallic"; color: "White" } - ListElement { text: "Emissive"; color: "White" } - ListElement { text: "Unlit"; color: "White" } - ListElement { text: "Occlusion"; color: "White" } - ListElement { text: "Lightmap"; color: "White" } - ListElement { text: "Scattering"; color: "White" } - ListElement { text: "Lighting"; color: "White" } - ListElement { text: "Shadow"; color: "White" } - ListElement { text: "Linear Depth"; color: "White" } - ListElement { text: "Half Linear Depth"; color: "White" } - ListElement { text: "Half Normal"; color: "White" } - ListElement { text: "Mid Curvature"; color: "White" } - ListElement { text: "Mid Normal"; color: "White" } - ListElement { text: "Low Curvature"; color: "White" } - ListElement { text: "Low Normal"; color: "White" } - ListElement { text: "Curvature Occlusion"; color: "White" } - ListElement { text: "Debug Scattering"; color: "White" } - ListElement { text: "Ambient Occlusion"; color: "White" } - ListElement { text: "Ambient Occlusion Blurred"; color: "White" } - ListElement { text: "Velocity"; color: "White" } - ListElement { text: "Custom"; color: "White" } } - width: 200 - onCurrentIndexChanged: { framebuffer.setDebugMode(currentIndex) } - } - } - - Row { - Column { - - CheckBox { - text: "Opaques" - checked: mainViewTask.getConfig("DrawOpaqueBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOpaqueBounds")["enabled"] = checked } - } - CheckBox { - text: "Transparents" - checked: mainViewTask.getConfig("DrawTransparentBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawTransparentBounds")["enabled"] = checked } - } - CheckBox { - text: "Overlay Opaques" - checked: mainViewTask.getConfig("DrawOverlayOpaqueBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOverlayOpaqueBounds")["enabled"] = checked } - } - CheckBox { - text: "Overlay Transparents" - checked: mainViewTask.getConfig("DrawOverlayTransparentBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawOverlayTransparentBounds")["enabled"] = checked } - } - } - Column { - CheckBox { - text: "Metas" - checked: mainViewTask.getConfig("DrawMetaBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawMetaBounds")["enabled"] = checked } - } - CheckBox { - text: "Lights" - checked: mainViewTask.getConfig("DrawLightBounds")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("DrawLightBounds")["enabled"] = checked; } - } - CheckBox { - text: "Zones" - checked: mainViewTask.getConfig("DrawZones")["enabled"] - onCheckedChanged: { mainViewTask.getConfig("ZoneRenderer")["enabled"] = checked; mainViewTask.getConfig("DrawZones")["enabled"] = checked; } + Column { + spacing: 10 + HifiControls.CheckBox { + boxSize: 20 + text: "Metas" + checked: render.mainViewTask.getConfig("DrawMetaBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawMetaBounds")["enabled"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Lights" + checked: render.mainViewTask.getConfig("DrawLightBounds")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("DrawLightBounds")["enabled"] = checked; } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Zones" + checked: render.mainViewTask.getConfig("DrawZones")["enabled"] + onCheckedChanged: { render.mainViewTask.getConfig("ZoneRenderer")["enabled"] = checked; render.mainViewTask.getConfig("DrawZones")["enabled"] = checked; } + } + } } } } } - diff --git a/scripts/developer/utilities/render/TestQML/luci.js b/scripts/developer/utilities/render/luci.js similarity index 94% rename from scripts/developer/utilities/render/TestQML/luci.js rename to scripts/developer/utilities/render/luci.js index ab77ebc016..3e0beb2181 100644 --- a/scripts/developer/utilities/render/TestQML/luci.js +++ b/scripts/developer/utilities/render/luci.js @@ -1,10 +1,9 @@ "use strict"; // -// gemstoneMagicMaker.js -// tablet-sample-app +// Luci.js +// tablet-engine app // -// Created by Faye Li on Feb 6 2017. // Copyright 2017 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -13,7 +12,7 @@ (function() { var TABLET_BUTTON_NAME = "LUCI"; - var QMLAPP_URL = Script.resolvePath("../deferredLighting.qml"); + var QMLAPP_URL = Script.resolvePath("./deferredLighting.qml"); var onLuciScreen = false; diff --git a/scripts/developer/utilities/tools/testEars.js b/scripts/developer/utilities/tools/testEars.js new file mode 100644 index 0000000000..600358eeb1 --- /dev/null +++ b/scripts/developer/utilities/tools/testEars.js @@ -0,0 +1,101 @@ +// +// testEars.js +// +// Positions and orients your listening position at a virtual head created in front of you. +// +// Created by David Rowe on 7 Oct 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 () { + + "use strict"; + + var overlays = [ + { + // Head + type: "sphere", + properties: { + dimensions: { x: 0.2, y: 0.3, z: 0.25 }, + position: { x: 0, y: 0, z: -2 }, + rotation: Quat.fromVec3Degrees({ x: 0, y: 180, z: 0 }), + color: { red: 128, green: 128, blue: 128 }, + alpha: 1.0, + solid: true + } + }, + { + // Left ear + type: "sphere", + properties: { + dimensions: { x: 0.04, y: 0.10, z: 0.08 }, + localPosition: { x: -0.1, y: 0, z: 0.05 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), + color: { red: 255, green: 0, blue: 0 }, // Red for "port". + alpha: 1.0, + solid: true + } + }, + { + // Right ear + type: "sphere", + properties: { + dimensions: { x: 0.04, y: 0.10, z: 0.08 }, + localPosition: { x: 0.1, y: 0, z: 0.05 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), + color: { red: 0, green: 255, blue: 0 }, // Green for "starboard". + alpha: 1.0, + solid: true + } + }, + { + // Nose + type: "sphere", + properties: { + dimensions: { x: 0.04, y: 0.04, z: 0.04 }, + localPosition: { x: 0, y: 0, z: -0.125 }, + localRotation: Quat.fromVec3Degrees({ x: 0, y: -0.08, z: 0 }), + color: { red: 160, green: 160, blue: 160 }, + alpha: 1.0, + solid: true + } + } + ], + originalListenerMode; + + function setUp() { + var i, length; + + originalListenerMode = MyAvatar.audioListenerMode; + + for (i = 0, length = overlays.length; i < length; i++) { + if (i === 0) { + overlays[i].properties.position = Vec3.sum(MyAvatar.getHeadPosition(), + Vec3.multiplyQbyV(MyAvatar.orientation, overlays[i].properties.position)); + overlays[i].properties.rotation = Quat.multiply(MyAvatar.orientation, overlays[i].properties.rotation); + } else { + overlays[i].properties.parentID = overlays[0].id; + } + overlays[i].id = Overlays.addOverlay(overlays[i].type, overlays[i].properties); + } + + MyAvatar.audioListenerMode = MyAvatar.audioListenerModeCustom; + MyAvatar.customListenPosition = overlays[0].properties.position; + MyAvatar.customListenOrientation = overlays[0].properties.orientation; + } + + function tearDown() { + var i, length; + for (i = 0, length = overlays.length; i < length; i++) { + Overlays.deleteOverlay(overlays[i].id); + } + + MyAvatar.audioListenerMode = originalListenerMode; + } + + Script.setTimeout(setUp, 2000); // Delay so that overlays display if script runs at Interface start. + Script.scriptEnding.connect(tearDown); +}()); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 7553ca4eeb..04b67ec14f 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -16,6 +16,8 @@ (function () { // BEGIN LOCAL_SCOPE Script.include("/~/system/libraries/accountUtils.js"); + var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; + // Function Name: onButtonClicked() // // Description: @@ -88,6 +90,9 @@ case 'goToPurchases': tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); break; + case 'goToMarketplaceItemPage': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; default: print('Unrecognized message from QML:', JSON.stringify(message)); } diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 0ef0e67471..2ef7b1efeb 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -119,6 +119,7 @@ Script.include("/~/system/libraries/controllers.js"); this.reticleMaxX; this.reticleMinY = MARGIN; this.reticleMaxY; + this.madeDynamic = false; var ACTION_TTL = 15; // seconds @@ -344,6 +345,13 @@ 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.localVelocity = {x: 0, y: 0, z: 0}; + Entities.editEntity(this.grabbedThingID, props); + this.madeDynamic = false; + } this.actionID = null; this.grabbedThingID = null; }; @@ -428,7 +436,6 @@ Script.include("/~/system/libraries/controllers.js"); this.distanceRotating = false; if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - this.updateLaserPointer(controllerData); this.prepareDistanceRotatingData(controllerData); return makeRunningValues(true, [], []); } else { @@ -503,7 +510,13 @@ Script.include("/~/system/libraries/controllers.js"); this.destroyContextOverlay(); } - if (entityIsDistanceGrabbable(targetProps)) { + if (entityIsGrabbable(targetProps)) { + if (!entityIsDistanceGrabbable(targetProps)) { + targetProps.dynamic = true; + Entities.editEntity(entityID, targetProps); + this.madeDynamic = true; + } + if (!this.distanceRotating) { this.grabbedThingID = entityID; this.grabbedDistance = rayPickInfo.distance; diff --git a/scripts/system/controllers/controllerModules/hudOverlayPointer.js b/scripts/system/controllers/controllerModules/hudOverlayPointer.js index 912eeccadb..fe1dedefb8 100644 --- a/scripts/system/controllers/controllerModules/hudOverlayPointer.js +++ b/scripts/system/controllers/controllerModules/hudOverlayPointer.js @@ -31,7 +31,7 @@ glow: 1.0, lineWidth: 5, ignoreRayIntersection: true, // always ignore this - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, parentID: AVATAR_SELF_ID }; var halfEnd = { @@ -40,7 +40,7 @@ color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, alpha: 0.9, ignoreRayIntersection: true, - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, visible: true }; var fullPath = { @@ -52,7 +52,7 @@ glow: 1.0, lineWidth: 5, ignoreRayIntersection: true, // always ignore this - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, parentID: AVATAR_SELF_ID }; var fullEnd = { @@ -61,7 +61,7 @@ color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, alpha: 0.9, ignoreRayIntersection: true, - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, visible: true }; var holdPath = { @@ -73,7 +73,7 @@ glow: 1.0, lineWidth: 5, ignoreRayIntersection: true, // always ignore this - drawHUDLayer: true, // Even when burried inside of something, show it. + drawHUDLayer: true, parentID: AVATAR_SELF_ID }; diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js new file mode 100644 index 0000000000..e3035b26f2 --- /dev/null +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -0,0 +1,108 @@ +"use strict"; + +// inVREditMode.js +// +// Created by David Rowe on 16 Sep 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 + +/* global Script, MyAvatar, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, + makeDispatcherModuleParameters, makeRunningValues, getEnabledModuleByName +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); + +(function () { + + function InVREditMode(hand) { + this.hand = hand; + this.disableModules = false; + this.parameters = makeDispatcherModuleParameters( + 200, // Not too high otherwise the tablet laser doesn't work. + this.hand === RIGHT_HAND + ? ["rightHand", "rightHandEquip", "rightHandTrigger"] + : ["leftHand", "leftHandEquip", "leftHandTrigger"], + [], + 100 + ); + + this.isReady = function (controllerData) { + if (this.disableModules) { + return makeRunningValues(true, [], []); + } + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + // Default behavior if disabling is not enabled. + if (!this.disableModules) { + return makeRunningValues(false, [], []); + } + + // Tablet stylus. + // Includes the tablet laser. + var tabletStylusInput = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightTabletStylusInput" + : "LeftTabletStylusInput"); + if (tabletStylusInput) { + var tabletReady = tabletStylusInput.isReady(controllerData); + if (tabletReady.active) { + return makeRunningValues(false, [], []); + } + } + + // Tablet grabbing. + var nearOverlay = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightNearParentingGrabOverlay" + : "LeftNearParentingGrabOverlay"); + if (nearOverlay) { + var nearOverlayReady = nearOverlay.isReady(controllerData); + if (nearOverlayReady.active && nearOverlay.grabbedThingID === HMD.tabletID) { + return makeRunningValues(false, [], []); + } + } + + // Teleport. + var teleporter = getEnabledModuleByName(this.hand === RIGHT_HAND + ? "RightTeleporter" + : "LeftTeleporter"); + if (teleporter) { + var teleporterReady = teleporter.isReady(controllerData); + if (teleporterReady.active) { + return makeRunningValues(false, [], []); + } + } + + // Other behaviors are disabled. + return makeRunningValues(true, [], []); + }; + } + + var leftHandInVREditMode = new InVREditMode(LEFT_HAND); + var rightHandInVREditMode = new InVREditMode(RIGHT_HAND); + enableDispatcherModule("LeftHandInVREditMode", leftHandInVREditMode); + enableDispatcherModule("RightHandInVREditMode", rightHandInVREditMode); + + var INVREDIT_DISABLER_MESSAGE_CHANNEL = "Hifi-InVREdit-Disabler"; + this.handleMessage = function (channel, message, sender) { + if (sender === MyAvatar.sessionUUID && channel === INVREDIT_DISABLER_MESSAGE_CHANNEL) { + if (message === "both") { + leftHandInVREditMode.disableModules = true; + rightHandInVREditMode.disableModules = true; + } else if (message === "none") { + leftHandInVREditMode.disableModules = false; + rightHandInVREditMode.disableModules = false; + } + } + }; + Messages.subscribe(INVREDIT_DISABLER_MESSAGE_CHANNEL); + Messages.messageReceived.connect(this.handleMessage); + + this.cleanup = function () { + disableDispatcherModule("LeftHandInVREditMode"); + disableDispatcherModule("RightHandInVREditMode"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/mouseHMD.js b/scripts/system/controllers/controllerModules/mouseHMD.js index 9ccf4912a1..1d8aeee1f9 100644 --- a/scripts/system/controllers/controllerModules/mouseHMD.js +++ b/scripts/system/controllers/controllerModules/mouseHMD.js @@ -114,8 +114,14 @@ this.run = function(controllerData, deltaTime) { var now = Date.now(); - if (this.mouseActivity.expired(now) || this.triggersPressed(controllerData, now)) { - Reticle.visible = false; + var hmdActive = HMD.active; + if (this.mouseActivity.expired(now) || this.triggersPressed(controllerData, now) || !hmdActive) { + if (!hmdActive) { + Reticle.visible = true; + } else { + Reticle.visible = false; + } + return ControllerDispatcherUtils.makeRunningValues(false, [], []); } this.adjustReticleDepth(controllerData); diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 36ed7920dd..29fa878cb1 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -248,17 +248,20 @@ Script.include("/~/system/libraries/controllers.js"); } }; - this.nearGrabWantsToRun = function(controllerData) { - var moduleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; - var module = getEnabledModuleByName(moduleName); - var ready = module ? module.isReady(controllerData) : makeRunningValues(false, [], []); - return ready.active; + this.otherModuleNeedsToRun = function(controllerData) { + var grabOverlayModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; + var grabOverlayModule = getEnabledModuleByName(grabOverlayModuleName); + var grabOverlayModuleReady = grabOverlayModule ? grabOverlayModule.isReady(controllerData) : makeRunningValues(false, [], []); + var farGrabModuleName = this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"; + var farGrabModule = getEnabledModuleByName(farGrabModuleName); + var farGrabModuleReady = farGrabModule ? farGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); + return grabOverlayModuleReady.active || farGrabModuleReady.active; }; this.processStylus = function(controllerData) { this.updateStylusTip(); - if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.nearGrabWantsToRun(controllerData)) { + if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.otherModuleNeedsToRun(controllerData)) { this.pointFinger(false); this.hideStylus(); return false; diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 2b4bc7e1fb..83576bd9a4 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -25,6 +25,7 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/overlayLaserInput.js", "controllerModules/webEntityLaserInput.js", "controllerModules/inEditMode.js", + "controllerModules/inVREditMode.js", "controllerModules/disableOtherModule.js", "controllerModules/farTrigger.js", "controllerModules/teleport.js", diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 0e9b8569ae..2f046cbce3 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -15,12 +15,13 @@ // /* global MyAvatar, Entities, Script, Camera, Vec3, Reticle, Overlays, getEntityCustomData, Messages, Quat, Controller, - isInEditMode, HMD */ + isInEditMode, HMD entityIsGrabbable*/ (function() { // BEGIN LOCAL_SCOPE -Script.include("/~/system/libraries/utils.js"); + Script.include("/~/system/libraries/utils.js"); + Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed var DELAY_FOR_30HZ = 33; // milliseconds @@ -330,9 +331,11 @@ Grabber.prototype.pressEvent = function(event) { return; } - var isDynamic = Entities.getEntityProperties(pickResults.objectID, "dynamic").dynamic; - if (!isDynamic) { - // only grab dynamic objects + var props = Entities.getEntityProperties(pickResults.objectID, ["dynamic", "userData", "locked", "type"]); + var isDynamic = props.dynamic; + var isGrabbable = props.grabbable; + if (!entityIsGrabbable(props)) { + // only grab grabbable objects return; } @@ -350,6 +353,7 @@ Grabber.prototype.pressEvent = function(event) { var entityProperties = Entities.getEntityProperties(clickedEntity); this.startPosition = entityProperties.position; this.lastRotation = entityProperties.rotation; + this.madeDynamic = false; var cameraPosition = Camera.getPosition(); var objectBoundingDiameter = Vec3.length(entityProperties.dimensions); @@ -361,6 +365,11 @@ Grabber.prototype.pressEvent = function(event) { return; } + if (entityIsGrabbable(props) && !isDynamic) { + entityProperties.dynamic = true; + Entities.editEntity(clickedEntity, entityProperties); + this.madeDynamic = true; + } // this.activateEntity(clickedEntity, entityProperties); this.isGrabbing = true; @@ -416,6 +425,14 @@ Grabber.prototype.releaseEvent = function(event) { if (this.actionID) { Entities.deleteAction(this.entityID, this.actionID); } + + if (this.madeDynamic) { + var entityProps = {}; + entityProps.dynamic = false; + entityProps.localVelocity = {x: 0, y: 0, z: 0}; + Entities.editEntity(this.entityID, entityProps); + } + this.actionID = null; LaserPointers.setRenderState(this.mouseRayEntities, ""); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 346f3626b9..d6d4de2a4b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -228,7 +228,7 @@ var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable"; var toolBar = (function () { - var EDIT_SETTING = "io.highfidelity.isEditting"; // for communication with other scripts + var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts var that = {}, toolBar, activeButton = null, @@ -1234,7 +1234,8 @@ Script.scriptEnding.connect(function () { Messages.messageReceived.disconnect(handleMessagesReceived); Messages.unsubscribe("entityToolUpdates"); - Messages.unsubscribe("Toolbar-DomainChanged"); + // Messages.unsubscribe("Toolbar-DomainChanged"); // Do not unsubscribe because the shapes.js app also subscribes and + // Messages.subscribe works script engine-wide which would mess things up if they're both run in the same engine. createButton = null; }); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index ded4542c51..fc16eae8bf 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -26,7 +26,7 @@ var xmlHttpRequest = null; var isPreparing = false; // Explicitly track download request status. - var confirmAllPurchases = false; // Set this to "true" to cause Checkout.qml to popup for all items, even if free + var commerceMode = false; var userIsLoggedIn = false; var walletNeedsSetup = false; @@ -99,7 +99,9 @@ } function maybeAddSetupWalletButton() { - if (userIsLoggedIn && walletNeedsSetup) { + if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) { + $('body').addClass("walletsetup-injected"); + var resultsElement = document.getElementById('results'); var setupWalletElement = document.createElement('div'); setupWalletElement.classList.add("row"); @@ -135,7 +137,8 @@ } function maybeAddLogInButton() { - if (!userIsLoggedIn) { + if (!$('body').hasClass("login-injected") && !userIsLoggedIn) { + $('body').addClass("login-injected"); var resultsElement = document.getElementById('results'); var logInElement = document.createElement('div'); logInElement.classList.add("row"); @@ -300,69 +303,72 @@ } function injectHiFiCode() { - if (!$('body').hasClass("code-injected") && confirmAllPurchases) { - - $('body').addClass("code-injected"); - + if (commerceMode) { maybeAddLogInButton(); maybeAddSetupWalletButton(); - changeDropdownMenu(); - var target = document.getElementById('templated-items'); - // MutationObserver is necessary because the DOM is populated after the page is loaded. - // We're searching for changes to the element whose ID is '#templated-items' - this is - // the element that gets filled in by the AJAX. - var observer = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - injectBuyButtonOnMainPage(); + if (!$('body').hasClass("code-injected")) { + + $('body').addClass("code-injected"); + changeDropdownMenu(); + + var target = document.getElementById('templated-items'); + // MutationObserver is necessary because the DOM is populated after the page is loaded. + // We're searching for changes to the element whose ID is '#templated-items' - this is + // the element that gets filled in by the AJAX. + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + injectBuyButtonOnMainPage(); + }); + //observer.disconnect(); }); - //observer.disconnect(); - }); - var config = { attributes: true, childList: true, characterData: true }; - observer.observe(target, config); + var config = { attributes: true, childList: true, characterData: true }; + observer.observe(target, config); - // Try this here in case it works (it will if the user just pressed the "back" button, - // since that doesn't trigger another AJAX request. - injectBuyButtonOnMainPage(); - maybeAddPurchasesButton(); + // Try this here in case it works (it will if the user just pressed the "back" button, + // since that doesn't trigger another AJAX request. + injectBuyButtonOnMainPage(); + maybeAddPurchasesButton(); + } } } function injectHiFiItemPageCode() { - if (!$('body').hasClass("code-injected") && confirmAllPurchases) { - - $('body').addClass("code-injected"); - + if (commerceMode) { maybeAddLogInButton(); - maybeAddSetupWalletButton(); - changeDropdownMenu(); - var purchaseButton = $('#side-info').find('.btn').first(); + if (!$('body').hasClass("code-injected")) { - var href = purchaseButton.attr('href'); - purchaseButton.attr('href', '#'); - purchaseButton.css({ - "background": "linear-gradient(#00b4ef, #0093C5)", - "color": "#FFF", - "font-weight": "600", - "padding-bottom": "10px" - }); + $('body').addClass("code-injected"); + changeDropdownMenu(); - var cost = $('.item-cost').text(); + var purchaseButton = $('#side-info').find('.btn').first(); - if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { - purchaseButton.html('PURCHASE ' + cost); + var href = purchaseButton.attr('href'); + purchaseButton.attr('href', '#'); + purchaseButton.css({ + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "padding-bottom": "10px" + }); + + var cost = $('.item-cost').text(); + + if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + purchaseButton.html('PURCHASE ' + cost); + } + + purchaseButton.on('click', function () { + buyButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + cost, + href); + }); + maybeAddPurchasesButton(); } - - purchaseButton.on('click', function () { - buyButtonClicked(window.location.pathname.split("/")[3], - $('#top-center').find('h1').text(), - $('#creator').find('.value').text(), - cost, - href); - }); - maybeAddPurchasesButton(); } } @@ -623,7 +629,7 @@ if (parsedJsonMessage.type === "marketplaces") { if (parsedJsonMessage.action === "commerceSetting") { - confirmAllPurchases = !!parsedJsonMessage.data.commerceMode; + commerceMode = !!parsedJsonMessage.data.commerceMode; userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn; walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup; injectCode(); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 4df25c41b7..9715cc454c 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -45,11 +45,12 @@ SelectionManager = (function() { return; } + var wantDebug = false; var messageParsed; try { messageParsed = JSON.parse(message); } catch (err) { - print("error -- entitySelectionTool got malformed message: " + message); + print("ERROR: entitySelectionTool.handleEntitySelectionToolUpdates - got malformed message: " + message); } // if (message === 'callUpdate') { @@ -57,7 +58,9 @@ SelectionManager = (function() { // } if (messageParsed.method === "selectEntity") { - print("setting selection to " + messageParsed.entityID); + if (wantDebug) { + print("setting selection to " + messageParsed.entityID); + } that.setSelections([messageParsed.entityID]); } } @@ -137,12 +140,12 @@ SelectionManager = (function() { if (entityID) { var idx = -1; for (var i = 0; i < that.selections.length; i++) { - if (entityID == that.selections[i]) { + if (entityID === that.selections[i]) { idx = i; break; } } - if (idx == -1) { + if (idx === -1) { that.selections.push(entityID); } else if (toggleSelection) { that.selections.splice(idx, 1); @@ -172,7 +175,7 @@ SelectionManager = (function() { that.localPosition = null; that.worldDimensions = null; that.worldPosition = null; - } else if (that.selections.length == 1) { + } else if (that.selections.length === 1) { properties = Entities.getEntityProperties(that.selections[0]); that.localDimensions = properties.dimensions; that.localPosition = properties.position; @@ -214,7 +217,7 @@ SelectionManager = (function() { that.worldPosition = { x: brn.x + (that.worldDimensions.x / 2), y: brn.y + (that.worldDimensions.y / 2), - z: brn.z + (that.worldDimensions.z / 2), + z: brn.z + (that.worldDimensions.z / 2) }; // For 1+ selections we can only modify selections in world space @@ -225,7 +228,7 @@ SelectionManager = (function() { try { listeners[j](selectionUpdated === true); } catch (e) { - print("EntitySelectionTool got exception: " + JSON.stringify(e)); + print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); } } }; @@ -235,8 +238,11 @@ SelectionManager = (function() { // Normalize degrees to be in the range (-180, 180] function normalizeDegrees(degrees) { - while (degrees > 180) degrees -= 360; - while (degrees <= -180) degrees += 360; + degrees = ((degrees + 180) % 360) - 180; + if (degrees <= -180) { + degrees += 360; + } + return degrees; } @@ -272,7 +278,6 @@ SelectionDisplay = (function() { var showExtendedStretchHandles = false; var spaceMode = SPACE_LOCAL; - var mode = "UNKNOWN"; var overlayNames = []; var lastCameraPosition = Camera.getPosition(); var lastCameraOrientation = Camera.getOrientation(); @@ -288,7 +293,6 @@ SelectionDisplay = (function() { }; var handleHoverAlpha = 1.0; - var rotateOverlayTargetSize = 10000; // really big target var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool var innerRadius; var outerRadius; @@ -298,19 +302,9 @@ SelectionDisplay = (function() { var yawCenter; var pitchCenter; var rollCenter; - var yawZero; - var pitchZero; - var rollZero; - var yawNormal; - var pitchNormal; - var rollNormal; + var rotZero; var rotationNormal; - var originalRotation; - var originalPitch; - var originalYaw; - var originalRoll; - var handleColor = { red: 255, @@ -382,7 +376,7 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var grabberPropertiesEdge = { @@ -399,7 +393,7 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var grabberPropertiesFace = { @@ -416,7 +410,7 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var grabberPropertiesCloner = { @@ -433,12 +427,12 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var spotLightLineProperties = { color: lightOverlayColor, - lineWidth: 1.5, + lineWidth: 1.5 }; var highlightBox = Overlays.addOverlay("cube", { @@ -478,7 +472,7 @@ SelectionDisplay = (function() { solid: false, visible: false, dashed: false, - lineWidth: 1.0, + lineWidth: 1.0 }); var selectionBoxes = []; @@ -514,7 +508,7 @@ SelectionDisplay = (function() { topMargin: 0, rightMargin: 0, bottomMargin: 0, - leftMargin: 0, + leftMargin: 0 }); var grabberMoveUp = Overlays.addOverlay("image3d", { @@ -530,7 +524,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: true, - drawInFront: true, + drawInFront: true }); // var normalLine = Overlays.addOverlay("line3d", { @@ -588,6 +582,12 @@ SelectionDisplay = (function() { var grabberSpotLightT = Overlays.addOverlay("cube", grabberPropertiesEdge); var grabberSpotLightB = Overlays.addOverlay("cube", grabberPropertiesEdge); + var spotLightGrabberHandles = [ + grabberSpotLightCircle, grabberSpotLightCenter, grabberSpotLightRadius, + grabberSpotLightLineT, grabberSpotLightLineB, grabberSpotLightLineL, grabberSpotLightLineR, + grabberSpotLightT, grabberSpotLightB, grabberSpotLightL, grabberSpotLightR + ]; + var grabberPointLightCircleX = Overlays.addOverlay("circle3d", { rotation: Quat.fromPitchYawRollDegrees(0, 90, 0), color: lightOverlayColor, @@ -613,6 +613,12 @@ SelectionDisplay = (function() { var grabberPointLightF = Overlays.addOverlay("cube", grabberPropertiesEdge); var grabberPointLightN = Overlays.addOverlay("cube", grabberPropertiesEdge); + var pointLightGrabberHandles = [ + grabberPointLightCircleX, grabberPointLightCircleY, grabberPointLightCircleZ, + grabberPointLightT, grabberPointLightB, grabberPointLightL, + grabberPointLightR, grabberPointLightF, grabberPointLightN + ]; + var grabberCloner = Overlays.addOverlay("cube", grabberPropertiesCloner); var stretchHandles = [ @@ -689,7 +695,7 @@ SelectionDisplay = (function() { width: 300, height: 200, rotation: baseOverlayRotation, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var yawOverlayAngles = { @@ -790,7 +796,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var rotateCurrentOverlay = Overlays.addOverlay("line3d", { @@ -811,28 +817,10 @@ SelectionDisplay = (function() { green: 0, blue: 255 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); - var rotateOverlayTarget = Overlays.addOverlay("circle3d", { - position: { - x: 0, - y: 0, - z: 0 - }, - size: rotateOverlayTargetSize, - color: { - red: 0, - green: 0, - blue: 0 - }, - alpha: 0.0, - solid: true, - visible: false, - rotation: yawOverlayRotation, - }); - var rotateOverlayInner = Overlays.addOverlay("circle3d", { position: { x: 0, @@ -864,7 +852,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var rotateOverlayOuter = Overlays.addOverlay("circle3d", { @@ -899,7 +887,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var rotateOverlayCurrent = Overlays.addOverlay("circle3d", { @@ -929,7 +917,7 @@ SelectionDisplay = (function() { red: 0, green: 0, blue: 0 - }, + } }); var yawHandle = Overlays.addOverlay("image3d", { @@ -945,7 +933,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: false, - drawInFront: true, + drawInFront: true }); @@ -962,7 +950,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: false, - drawInFront: true, + drawInFront: true }); @@ -979,7 +967,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: false, - drawInFront: true, + drawInFront: true }); var allOverlays = [ @@ -989,7 +977,6 @@ SelectionDisplay = (function() { yawHandle, pitchHandle, rollHandle, - rotateOverlayTarget, rotateOverlayInner, rotateOverlayOuter, rotateOverlayCurrent, @@ -1003,7 +990,7 @@ SelectionDisplay = (function() { grabberSpotLightCircle, grabberPointLightCircleX, grabberPointLightCircleY, - grabberPointLightCircleZ, + grabberPointLightCircleZ ].concat(stretchHandles); @@ -1044,7 +1031,6 @@ SelectionDisplay = (function() { overlayNames[pitchHandle] = "pitchHandle"; overlayNames[rollHandle] = "rollHandle"; - overlayNames[rotateOverlayTarget] = "rotateOverlayTarget"; overlayNames[rotateOverlayInner] = "rotateOverlayInner"; overlayNames[rotateOverlayOuter] = "rotateOverlayOuter"; overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent"; @@ -1059,7 +1045,7 @@ SelectionDisplay = (function() { // But we dont' get mousePressEvents. that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); Script.scriptEnding.connect(that.triggerMapping.disable); - that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. + that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. that.TRIGGER_ON_VALUE = 0.4; that.TRIGGER_OFF_VALUE = 0.15; that.triggered = false; @@ -1100,12 +1086,37 @@ SelectionDisplay = (function() { return controllerComputePickRay() || Camera.computePickRay(x, y); } function addGrabberTool(overlay, tool) { - grabberTools[overlay] = { - mode: tool.mode, - onBegin: tool.onBegin, - onMove: tool.onMove, - onEnd: tool.onEnd, - }; + grabberTools[overlay] = tool; + return tool; + } + + // @param: toolHandle: The overlayID associated with the tool + // that correlates to the tool you wish to query. + // @note: If toolHandle is null or undefined then activeTool + // will be checked against those values as opposed to + // the tool registered under toolHandle. Null & Undefined + // are treated as separate values. + // @return: bool - Indicates if the activeTool is that queried. + function isActiveTool(toolHandle) { + if (!toolHandle) { + // Allow isActiveTool(null) and similar to return true if there's + // no active tool + return (activeTool === toolHandle); + } + + if (!grabberTools.hasOwnProperty(toolHandle)) { + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be egistered via addGrabberTool."); + // EARLY EXIT + return false; + } + + return (activeTool === grabberTools[ toolHandle ]); + } + + // @return string - The mode of the currently active tool; + // otherwise, "UNKNOWN" if there's no active tool. + function getMode() { + return (activeTool ? activeTool.mode : "UNKNOWN"); } @@ -1162,8 +1173,8 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION HANDLES that.updateRotationHandles = function() { - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); + var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(SelectionManager.worldDimensions, 0.5); var innerActive = false; var innerAlpha = 0.2; var outerAlpha = 0.2; @@ -1178,7 +1189,7 @@ SelectionDisplay = (function() { var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; var dimensions, rotation; - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { rotation = SelectionManager.localRotation; } else { rotation = SelectionManager.worldRotation; @@ -1235,22 +1246,6 @@ SelectionDisplay = (function() { z: 0 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1312,23 +1307,6 @@ SelectionDisplay = (function() { z: 90 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - - yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1392,22 +1370,6 @@ SelectionDisplay = (function() { z: 180 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1467,22 +1429,6 @@ SelectionDisplay = (function() { z: 180 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1529,34 +1475,31 @@ SelectionDisplay = (function() { var rotateHandlesVisible = true; var rotationOverlaysVisible = false; - var translateHandlesVisible = true; - var stretchHandlesVisible = true; - var selectionBoxVisible = true; + // note: Commented out as these are currently unused here; however, + // leaving them around as they document intent of state as it + // relates to modes that may be useful later. + // var translateHandlesVisible = true; + // var selectionBoxVisible = true; var isPointLight = false; - - if (selectionManager.selections.length == 1) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); - isPointLight = properties.type == "Light" && !properties.isSpotlight; + if (SelectionManager.selections.length === 1) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + isPointLight = (properties.type === "Light") && !properties.isSpotlight; } - if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_X in case they Z") { + if (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || + isActiveTool(rollHandle) || isActiveTool(selectionBox) || isActiveTool(grabberCloner)) { rotationOverlaysVisible = true; rotateHandlesVisible = false; - translateHandlesVisible = false; - stretchHandlesVisible = false; - selectionBoxVisible = false; - } else if (mode == "TRANSLATE_UP_DOWN" || isPointLight) { + // translateHandlesVisible = false; + // selectionBoxVisible = false; + } else if (isActiveTool(grabberMoveUp) || isPointLight) { rotateHandlesVisible = false; - stretchHandlesVisible = false; - } else if (mode != "UNKNOWN") { + } else if (activeTool) { // every other mode is a stretch mode... rotateHandlesVisible = false; - translateHandlesVisible = false; + // translateHandlesVisible = false; } - Overlays.editOverlay(rotateOverlayTarget, { - visible: rotationOverlaysVisible - }); Overlays.editOverlay(rotateZeroOverlay, { visible: rotationOverlaysVisible }); @@ -1581,22 +1524,87 @@ SelectionDisplay = (function() { }); }; + // FUNCTION: UPDATE HANDLE SIZES + that.updateHandleSizes = function() { + if (SelectionManager.hasSelection()) { + var diff = Vec3.subtract(SelectionManager.worldPosition, Camera.getPosition()); + var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; + var dimensions = SelectionManager.worldDimensions; + var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; + grabberSize = Math.min(grabberSize, avgDimension / 10); + + for (var i = 0; i < stretchHandles.length; i++) { + Overlays.editOverlay(stretchHandles[i], { + size: grabberSize + }); + } + var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7; + handleSize = Math.min(handleSize, avgDimension / 3); + + Overlays.editOverlay(yawHandle, { + scale: handleSize + }); + Overlays.editOverlay(pitchHandle, { + scale: handleSize + }); + Overlays.editOverlay(rollHandle, { + scale: handleSize + }); + var pos = Vec3.sum(grabberMoveUpPosition, { + x: 0, + y: Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3, + z: 0 + }); + Overlays.editOverlay(grabberMoveUp, { + position: pos, + scale: handleSize / 1.25 + }); + } + }; + Script.update.connect(that.updateHandleSizes); + // FUNCTION: SET SPACE MODE that.setSpaceMode = function(newSpaceMode) { - if (spaceMode != newSpaceMode) { + var wantDebug = false; + if (wantDebug) { + print("======> SetSpaceMode called. ========"); + } + + if (spaceMode !== newSpaceMode) { + if (wantDebug) { + print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); + } spaceMode = newSpaceMode; that.updateHandles(); + } else if (wantDebug) { + print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); + } + if (wantDebug) { + print("====== SetSpaceMode called. <========"); } }; // FUNCTION: TOGGLE SPACE MODE that.toggleSpaceMode = function() { - if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) { - print("Local space editing is not available with multiple selections"); + var wantDebug = false; + if (wantDebug) { + print("========> ToggleSpaceMode called. ========="); + } + if ((spaceMode === SPACE_WORLD) && (SelectionManager.selections.length > 1)) { + if (wantDebug) { + print("Local space editing is not available with multiple selections"); + } return; } - spaceMode = spaceMode == SPACE_LOCAL ? SPACE_WORLD : SPACE_LOCAL; + if (wantDebug) { + print("PreToggle: " + spaceMode); + } + spaceMode = (spaceMode === SPACE_LOCAL) ? SPACE_WORLD : SPACE_LOCAL; that.updateHandles(); + if (wantDebug) { + print("PostToggle: " + spaceMode); + print("======== ToggleSpaceMode called. <========="); + } }; // FUNCTION: UNSELECT ALL @@ -1605,16 +1613,24 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE HANDLES that.updateHandles = function() { + var wantDebug = false; + if (wantDebug) { + print("======> Update Handles ======="); + print(" Selections Count: " + SelectionManager.selections.length); + print(" SpaceMode: " + spaceMode); + print(" DisplayMode: " + getMode()); + } if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); return; } + // print(" Triggering updateRotationHandles"); that.updateRotationHandles(); var rotation, dimensions, position, registrationPoint; - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { rotation = SelectionManager.localRotation; dimensions = SelectionManager.localDimensions; position = SelectionManager.localPosition; @@ -1629,11 +1645,11 @@ SelectionDisplay = (function() { var registrationPointDimensions = { x: dimensions.x * registrationPoint.x, y: dimensions.y * registrationPoint.y, - z: dimensions.z * registrationPoint.z, + z: dimensions.z * registrationPoint.z }; // Center of entity, relative to registration point - center = getRelativeCenterPosition(dimensions, registrationPoint); + var center = getRelativeCenterPosition(dimensions, registrationPoint); // Distances in world coordinates relative to the registration point var left = -registrationPointDimensions.x; @@ -1837,265 +1853,164 @@ SelectionDisplay = (function() { EdgeFR = Vec3.sum(position, EdgeFR); EdgeFL = Vec3.sum(position, EdgeFL); - var stretchHandlesVisible = spaceMode == SPACE_LOCAL; - var extendedStretchHandlesVisible = stretchHandlesVisible && showExtendedStretchHandles; - - if (selectionManager.selections.length == 1) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); - if (properties.type == "Light" && properties.isSpotlight) { - stretchHandlesVisible = false; - extendedStretchHandlesVisible = false; - - Overlays.editOverlay(grabberSpotLightCenter, { - position: position, - visible: false, - }); - Overlays.editOverlay(grabberSpotLightRadius, { - position: NEAR, - rotation: rotation, - visible: true, - }); - var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); - - Overlays.editOverlay(grabberSpotLightL, { - position: EdgeNL, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightR, { - position: EdgeNR, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightT, { - position: EdgeTN, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightB, { - position: EdgeBN, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightCircle, { - position: NEAR, - dimensions: { - x: distance, - y: distance, - z: 1 - }, - lineWidth: 1.5, - rotation: rotation, - visible: true, - }); - - Overlays.editOverlay(grabberSpotLightLineT, { - start: position, - end: EdgeTN, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightLineB, { - start: position, - end: EdgeBN, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightLineR, { - start: position, - end: EdgeNR, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightLineL, { - start: position, - end: EdgeNL, - visible: true, - }); - - Overlays.editOverlay(grabberPointLightCircleX, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleY, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - visible: false - }); - Overlays.editOverlay(grabberPointLightT, { - visible: false - }); - Overlays.editOverlay(grabberPointLightB, { - visible: false - }); - Overlays.editOverlay(grabberPointLightL, { - visible: false - }); - Overlays.editOverlay(grabberPointLightR, { - visible: false - }); - Overlays.editOverlay(grabberPointLightF, { - visible: false - }); - Overlays.editOverlay(grabberPointLightN, { - visible: false - }); - } else if (properties.type == "Light" && !properties.isSpotlight) { - stretchHandlesVisible = false; - extendedStretchHandlesVisible = false; - Overlays.editOverlay(grabberPointLightT, { - position: TOP, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightB, { - position: BOTTOM, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightL, { - position: LEFT, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightR, { - position: RIGHT, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightF, { - position: FAR, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightN, { - position: NEAR, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightCircleX, { - position: position, - rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)), - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true, - }); - Overlays.editOverlay(grabberPointLightCircleY, { - position: position, - rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)), - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true, - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - position: position, - rotation: rotation, - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true, - }); - - Overlays.editOverlay(grabberSpotLightRadius, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightB, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightCircle, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineB, { - visible: false - }); - } else { - Overlays.editOverlay(grabberSpotLightCenter, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightRadius, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightB, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightCircle, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineB, { - visible: false - }); - - Overlays.editOverlay(grabberPointLightCircleX, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleY, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - visible: false - }); - Overlays.editOverlay(grabberPointLightT, { - visible: false - }); - Overlays.editOverlay(grabberPointLightB, { - visible: false - }); - Overlays.editOverlay(grabberPointLightL, { - visible: false - }); - Overlays.editOverlay(grabberPointLightR, { - visible: false - }); - Overlays.editOverlay(grabberPointLightF, { - visible: false - }); - Overlays.editOverlay(grabberPointLightN, { - visible: false - }); - } + var inModeRotate = (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)); + var inModeTranslate = (isActiveTool(selectionBox) || isActiveTool(grabberCloner) || isActiveTool(grabberMoveUp)); + var stretchHandlesVisible = !(inModeRotate || inModeTranslate) && (spaceMode === SPACE_LOCAL); + var extendedStretchHandlesVisible = (stretchHandlesVisible && showExtendedStretchHandles); + var cloneHandleVisible = !(inModeRotate || inModeTranslate); + if (wantDebug) { + print(" Set Non-Light Grabbers Visible - Norm: " + stretchHandlesVisible + " Ext: " + extendedStretchHandlesVisible); } + var isSingleSelection = (SelectionManager.selections.length === 1); + if (isSingleSelection) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + var isLightSelection = (properties.type === "Light"); + if (isLightSelection) { + if (wantDebug) { + print(" Light Selection revoking Non-Light Grabbers Visibility!"); + } + stretchHandlesVisible = false; + extendedStretchHandlesVisible = false; + cloneHandleVisible = false; + if (properties.isSpotlight) { + that.setPointLightHandlesVisible(false); + + var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); + var showEdgeSpotGrabbers = !(inModeTranslate || inModeRotate); + Overlays.editOverlay(grabberSpotLightCenter, { + position: position, + visible: false + }); + Overlays.editOverlay(grabberSpotLightRadius, { + position: NEAR, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + + Overlays.editOverlay(grabberSpotLightL, { + position: EdgeNL, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + Overlays.editOverlay(grabberSpotLightR, { + position: EdgeNR, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + Overlays.editOverlay(grabberSpotLightT, { + position: EdgeTN, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + Overlays.editOverlay(grabberSpotLightB, { + position: EdgeBN, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + Overlays.editOverlay(grabberSpotLightCircle, { + position: NEAR, + dimensions: { + x: distance, + y: distance, + z: 1 + }, + lineWidth: 1.5, + rotation: rotation, + visible: true + }); + + Overlays.editOverlay(grabberSpotLightLineT, { + start: position, + end: EdgeTN, + visible: true + }); + Overlays.editOverlay(grabberSpotLightLineB, { + start: position, + end: EdgeBN, + visible: true + }); + Overlays.editOverlay(grabberSpotLightLineR, { + start: position, + end: EdgeNR, + visible: true + }); + Overlays.editOverlay(grabberSpotLightLineL, { + start: position, + end: EdgeNL, + visible: true + }); + + } else { // ..it's a PointLight + that.setSpotLightHandlesVisible(false); + + var showEdgePointGrabbers = !inModeTranslate; + Overlays.editOverlay(grabberPointLightT, { + position: TOP, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightB, { + position: BOTTOM, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightL, { + position: LEFT, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightR, { + position: RIGHT, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightF, { + position: FAR, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightN, { + position: NEAR, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightCircleX, { + position: position, + rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)), + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, + visible: true + }); + Overlays.editOverlay(grabberPointLightCircleY, { + position: position, + rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)), + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, + visible: true + }); + Overlays.editOverlay(grabberPointLightCircleZ, { + position: position, + rotation: rotation, + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, + visible: true + }); + } + } else { // ..it's not a light at all + that.setSpotLightHandlesVisible(false); + that.setPointLightHandlesVisible(false); + } + }// end of isSingleSelection Overlays.editOverlay(grabberLBN, { @@ -2172,7 +2087,7 @@ SelectionDisplay = (function() { }); Overlays.editOverlay(grabberCloner, { - visible: true, + visible: cloneHandleVisible, rotation: rotation, position: EdgeTR }); @@ -2183,11 +2098,11 @@ SelectionDisplay = (function() { position: selectionBoxPosition, dimensions: dimensions, rotation: rotation, - visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"), + visible: !inModeRotate }); // Create more selection box overlays if we don't have enough - var overlaysNeeded = selectionManager.selections.length - selectionBoxes.length; + var overlaysNeeded = SelectionManager.selections.length - selectionBoxes.length; for (var i = 0; i < overlaysNeeded; i++) { selectionBoxes.push( Overlays.addOverlay("cube", { @@ -2207,15 +2122,15 @@ SelectionDisplay = (function() { visible: false, dashed: false, lineWidth: 1.0, - ignoreRayIntersection: true, + ignoreRayIntersection: true })); } i = 0; // Only show individual selections boxes if there is more than 1 selection - if (selectionManager.selections.length > 1) { - for (; i < selectionManager.selections.length; i++) { - var props = Entities.getEntityProperties(selectionManager.selections[i]); + if (SelectionManager.selections.length > 1) { + for (; i < SelectionManager.selections.length; i++) { + var props = Entities.getEntityProperties(SelectionManager.selections[i]); // Adjust overlay position to take registrationPoint into account // centeredRP = registrationPoint with range [-0.5, 0.5] @@ -2230,14 +2145,16 @@ SelectionDisplay = (function() { var curBoxPosition = Vec3.sum(props.position, offset); var color = {red: 255, green: 128, blue: 0}; - if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64}; + if (i >= SelectionManager.selections.length - 1) { + color = {red: 255, green: 255, blue: 64}; + } Overlays.editOverlay(selectionBoxes[i], { position: curBoxPosition, color: color, rotation: props.rotation, dimensions: props.dimensions, - visible: true, + visible: true }); } } @@ -2316,42 +2233,85 @@ SelectionDisplay = (function() { z: position.z }; Overlays.editOverlay(grabberMoveUp, { - visible: (activeTool === null) || (mode == "TRANSLATE_UP_DOWN") + visible: (!activeTool) || isActiveTool(grabberMoveUp) }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL", + visible: !inModeRotate, solid: true, position: { - x: selectionManager.worldPosition.x, + x: SelectionManager.worldPosition.x, y: grid.getOrigin().y, - z: selectionManager.worldPosition.z + z: SelectionManager.worldPosition.z }, dimensions: { - x: selectionManager.worldDimensions.x, - y: selectionManager.worldDimensions.z + x: SelectionManager.worldDimensions.x, + y: SelectionManager.worldDimensions.z }, - rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0) }); + if (wantDebug) { + print("====== Update Handles <======="); + } + }; + function helperSetOverlaysVisibility(handleArray, isVisible) { + var numHandles = handleArray.length; + var visibilityUpdate = { visible: isVisible }; + for (var handleIndex = 0; handleIndex < numHandles; ++handleIndex) { + Overlays.editOverlay(handleArray[ handleIndex ], visibilityUpdate); + } + } + // FUNCTION: SET OVERLAYS VISIBLE that.setOverlaysVisible = function(isVisible) { - var length = allOverlays.length; - for (var overlayIndex = 0; overlayIndex < length; overlayIndex++) { - Overlays.editOverlay(allOverlays[overlayIndex], { - visible: isVisible - }); - } - length = selectionBoxes.length; - for (var boxIndex = 0; boxIndex < length; boxIndex++) { - Overlays.editOverlay(selectionBoxes[boxIndex], { - visible: isVisible - }); + helperSetOverlaysVisibility(allOverlays, isVisible); + helperSetOverlaysVisibility(selectionBoxes, isVisible); + }; + + // FUNCTION: SET ROTATION HANDLES VISIBLE + that.setRotationHandlesVisible = function(isVisible) { + var visibilityUpdate = { visible: isVisible }; + Overlays.editOverlay(yawHandle, visibilityUpdate); + Overlays.editOverlay(pitchHandle, visibilityUpdate); + Overlays.editOverlay(rollHandle, visibilityUpdate); + }; + + // FUNCTION: SET STRETCH HANDLES VISIBLE + that.setStretchHandlesVisible = function(isVisible) { + helperSetOverlaysVisibility(stretchHandles, isVisible); + }; + + // FUNCTION: SET GRABBER MOVE UP VISIBLE + that.setGrabberMoveUpVisible = function(isVisible) { + Overlays.editOverlay(grabberMoveUp, { visible: isVisible }); + }; + + // FUNCTION: SET GRABBER TOOLS UP VISIBLE + that.setGrabberToolsVisible = function(isVisible) { + var visibilityUpdate = { visible: isVisible }; + for (var toolKey in grabberTools) { + if (!grabberTools.hasOwnProperty(toolKey)) { + // EARLY ITERATION EXIT--(On to the next one) + continue; + } + + Overlays.editOverlay(toolKey, visibilityUpdate); } }; + // FUNCTION: SET POINT LIGHT HANDLES VISIBLE + that.setPointLightHandlesVisible = function(isVisible) { + helperSetOverlaysVisibility(pointLightGrabberHandles, isVisible); + }; + + // FUNCTION: SET SPOT LIGHT HANDLES VISIBLE + that.setSpotLightHandlesVisible = function(isVisible) { + helperSetOverlaysVisibility(spotLightGrabberHandles, isVisible); + }; + // FUNCTION: UNSELECT // TODO?: Needs implementation that.unselect = function(entityID) {}; @@ -2363,18 +2323,38 @@ SelectionDisplay = (function() { var duplicatedEntityIDs = null; // TOOL DEFINITION: TRANSLATE XZ TOOL - var translateXZTool = { + var translateXZTool = addGrabberTool(selectionBox,{ mode: 'TRANSLATE_XZ', pickPlanePosition: { x: 0, y: 0, z: 0 }, greatestDimension: 0.0, startingDistance: 0.0, startingElevation: 0.0, - onBegin: function(event,isAltFromGrab) { - SelectionManager.saveProperties(); - startPosition = SelectionManager.worldPosition; - var dimensions = SelectionManager.worldDimensions; + onBegin: function(event, pickRay, pickResult, doClone) { + var wantDebug = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(Beg) -> ======================="); + Vec3.print(" pickRay", pickRay); + Vec3.print(" pickRay.origin", pickRay.origin); + Vec3.print(" pickResult.intersection", pickResult.intersection); + } + + SelectionManager.saveProperties(); + that.setRotationHandlesVisible(false); + that.setStretchHandlesVisible(false); + that.setGrabberMoveUpVisible(false); + + startPosition = SelectionManager.worldPosition; + + translateXZTool.pickPlanePosition = pickResult.intersection; + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); + translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); + translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); + if (wantDebug) { + print(" longest dimension: " + translateXZTool.greatestDimension); + print(" starting distance: " + translateXZTool.startingDistance); + print(" starting elevation: " + translateXZTool.startingElevation); + } - var pickRay = generalComputePickRay(event.x, event.y); initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { x: 0, y: 1, @@ -2384,7 +2364,7 @@ SelectionDisplay = (function() { // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not // the new ones. - if (event.isAlt || isAltFromGrab) { + if (event.isAlt || doClone) { duplicatedEntityIDs = []; for (var otherEntityID in SelectionManager.savedProperties) { var properties = SelectionManager.savedProperties[otherEntityID]; @@ -2392,7 +2372,7 @@ SelectionDisplay = (function() { var entityID = Entities.addEntity(properties); duplicatedEntityIDs.push({ entityID: entityID, - properties: properties, + properties: properties }); } } @@ -2401,6 +2381,9 @@ SelectionDisplay = (function() { } isConstrained = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(End) <- ======================="); + } }, onEnd: function(event, reason) { pushCommandForSelections(duplicatedEntityIDs); @@ -2429,29 +2412,33 @@ SelectionDisplay = (function() { // this will happen when someone drags across the horizon from the side they started on. if (!pick) { if (wantDebug) { - print("Pick ray does not intersect XZ plane."); + print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane."); } + + // EARLY EXIT--(Invalid ray detected.) return; } var vector = Vec3.subtract(pick, initialXZPick); // If the mouse is too close to the horizon of the pick plane, stop moving - var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it + var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it var elevation = translateXZTool.elevation(pickRay.origin, pick); if (wantDebug) { - print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); + print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); } if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { if (wantDebug) { - print("too close to horizon!"); + print(" "+ translateXZTool.mode + " - too close to horizon!"); } + + // EARLY EXIT--(Don't proceed past the reached limit.) return; } // If the angular size of the object is too small, stop moving - var MIN_ANGULAR_SIZE = 0.01; // Radians + var MIN_ANGULAR_SIZE = 0.01; // Radians if (translateXZTool.greatestDimension > 0) { var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick)); if (wantDebug) { @@ -2518,13 +2505,12 @@ SelectionDisplay = (function() { } constrainMajorOnly = event.isControl; - var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, selectionManager.worldDimensions)); + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions)); vector = Vec3.subtract( grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), cornerPosition); - for (var i = 0; i < SelectionManager.selections.length; i++) { var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; if (!properties) { @@ -2536,7 +2522,7 @@ SelectionDisplay = (function() { z: vector.z }); Entities.editEntity(SelectionManager.selections[i], { - position: newPosition, + position: newPosition }); if (wantDebug) { @@ -2549,23 +2535,24 @@ SelectionDisplay = (function() { SelectionManager._update(); } - }; - + }); + // GRABBER TOOL: GRABBER MOVE UP var lastXYPick = null; var upDownPickNormal = null; addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", - onBegin: function(event) { - pickRay = generalComputePickRay(event.x, event.y); - + onBegin: function(event, pickRay, pickResult) { upDownPickNormal = Quat.getForward(lastCameraOrientation); - // Remove y component so the y-axis lies along the plane we picking on - this will + // Remove y component so the y-axis lies along the plane we're picking on - this will // give movements that follow the mouse. upDownPickNormal.y = 0; lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); SelectionManager.saveProperties(); + that.setGrabberMoveUpVisible(true); + that.setStretchHandlesVisible(false); + that.setRotationHandlesVisible(false); // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not @@ -2578,7 +2565,7 @@ SelectionDisplay = (function() { var entityID = Entities.addEntity(properties); duplicatedEntityIDs.push({ entityID: entityID, - properties: properties, + properties: properties }); } } @@ -2608,36 +2595,30 @@ SelectionDisplay = (function() { print(" event.y:" + event.y); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - //Vec3.print(" newPosition:", newPosition); + // Vec3.print(" newPosition:", newPosition); } for (var i = 0; i < SelectionManager.selections.length; i++) { var id = SelectionManager.selections[i]; - var properties = selectionManager.savedProperties[id]; + var properties = SelectionManager.savedProperties[id]; var original = properties.position; var newPosition = Vec3.sum(properties.position, vector); Entities.editEntity(id, { - position: newPosition, + position: newPosition }); } SelectionManager._update(); - }, + } }); // GRABBER TOOL: GRABBER CLONER addGrabberTool(grabberCloner, { mode: "CLONE", - onBegin: function(event) { - - var pickRay = generalComputePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay); - translateXZTool.pickPlanePosition = result.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), - SelectionManager.worldDimensions.z); - - translateXZTool.onBegin(event,true); + onBegin: function(event, pickRay, pickResult) { + var doClone = true; + translateXZTool.onBegin(event,pickRay,pickResult,doClone); }, elevation: function (event) { translateXZTool.elevation(event); @@ -2653,14 +2634,13 @@ SelectionDisplay = (function() { }); - // FUNCTION: VEC 3 MULT var vec3Mult = function(v1, v2) { - return { - x: v1.x * v2.x, - y: v1.y * v2.y, - z: v1.z * v2.z - }; + return { + x: v1.x * v2.x, + y: v1.y * v2.y, + z: v1.z * v2.z + }; }; // FUNCTION: MAKE STRETCH TOOL @@ -2668,6 +2648,7 @@ SelectionDisplay = (function() { // direction - direction to stretch in // pivot - point to use as a pivot // offset - the position of the overlay tool relative to the selections center position + // @return: tool obj var makeStretchTool = function(stretchMode, direction, pivot, offset, customOnMove) { // directionFor3DStretch - direction and pivot for 3D stretch // distanceFor3DStretch - distance from the intersection point and the handController @@ -2682,16 +2663,15 @@ SelectionDisplay = (function() { var signs = { x: direction.x < 0 ? -1 : (direction.x > 0 ? 1 : 0), y: direction.y < 0 ? -1 : (direction.y > 0 ? 1 : 0), - z: direction.z < 0 ? -1 : (direction.z > 0 ? 1 : 0), + z: direction.z < 0 ? -1 : (direction.z > 0 ? 1 : 0) }; var mask = { x: Math.abs(direction.x) > 0 ? 1 : 0, y: Math.abs(direction.y) > 0 ? 1 : 0, - z: Math.abs(direction.z) > 0 ? 1 : 0, + z: Math.abs(direction.z) > 0 ? 1 : 0 }; - - + var numDimensions = mask.x + mask.y + mask.z; @@ -2709,12 +2689,12 @@ SelectionDisplay = (function() { var pickRayPosition3D = null; var rotation = null; - var onBegin = function(event) { + var onBegin = function(event, pickRay, pickResult) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; - rotation = spaceMode == SPACE_LOCAL ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0); + rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0); - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { rotation = SelectionManager.localRotation; initialPosition = SelectionManager.localPosition; initialDimensions = SelectionManager.localDimensions; @@ -2753,14 +2733,13 @@ SelectionDisplay = (function() { deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D); var scaledOffsetWorld3D = vec3Mult(initialDimensions, - Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), - centeredRP)); + Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), centeredRP)); pickRayPosition3D = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); } var start = null; var end = null; - if (numDimensions == 1 && mask.x) { + if ((numDimensions === 1) && mask.x) { start = Vec3.multiplyQbyV(rotation, { x: -10000, y: 0, @@ -2776,10 +2755,10 @@ SelectionDisplay = (function() { Overlays.editOverlay(xRailOverlay, { start: start, end: end, - visible: true, + visible: true }); } - if (numDimensions == 1 && mask.y) { + if ((numDimensions === 1) && mask.y) { start = Vec3.multiplyQbyV(rotation, { x: 0, y: -10000, @@ -2795,10 +2774,10 @@ SelectionDisplay = (function() { Overlays.editOverlay(yRailOverlay, { start: start, end: end, - visible: true, + visible: true }); } - if (numDimensions == 1 && mask.z) { + if ((numDimensions === 1) && mask.z) { start = Vec3.multiplyQbyV(rotation, { x: 0, y: 0, @@ -2814,17 +2793,17 @@ SelectionDisplay = (function() { Overlays.editOverlay(zRailOverlay, { start: start, end: end, - visible: true, + visible: true }); } - if (numDimensions == 1) { - if (mask.x == 1) { + if (numDimensions === 1) { + if (mask.x === 1) { planeNormal = { x: 0, y: 1, z: 0 }; - } else if (mask.y == 1) { + } else if (mask.y === 1) { planeNormal = { x: 1, y: 0, @@ -2837,7 +2816,7 @@ SelectionDisplay = (function() { z: 0 }; } - } else if (numDimensions == 2) { + } else if (numDimensions === 2) { if (mask.x === 0) { planeNormal = { x: 1, @@ -2854,13 +2833,12 @@ SelectionDisplay = (function() { planeNormal = { x: 0, y: 0, - z: z + z: 1 }; } } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - var pickRay = generalComputePickRay(event.x, event.y); lastPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); @@ -2895,10 +2873,10 @@ SelectionDisplay = (function() { }; var onMove = function(event) { - var proportional = spaceMode == SPACE_WORLD || event.isShifted || activeTool.mode == "STRETCH_RADIUS"; + var proportional = (spaceMode === SPACE_WORLD) || event.isShifted || isActiveTool(grabberSpotLightRadius); var position, dimensions, rotation; - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { position = SelectionManager.localPosition; dimensions = SelectionManager.localDimensions; rotation = SelectionManager.localRotation; @@ -2935,8 +2913,8 @@ SelectionDisplay = (function() { } else { newPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); + pickRayPosition, + planeNormal); vector = Vec3.subtract(newPick, lastPick); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); @@ -2984,7 +2962,7 @@ SelectionDisplay = (function() { for (var i = 0; i < SelectionManager.selections.length; i++) { Entities.editEntity(SelectionManager.selections[i], { position: newPosition, - dimensions: newDimensions, + dimensions: newDimensions }); } @@ -2992,10 +2970,10 @@ SelectionDisplay = (function() { var wantDebug = false; if (wantDebug) { print(stretchMode); - //Vec3.print(" newIntersection:", newIntersection); + // Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - //Vec3.print(" oldPOS:", oldPOS); - //Vec3.print(" newPOS:", newPOS); + // Vec3.print(" oldPOS:", oldPOS); + // Vec3.print(" newPOS:", newPOS); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); @@ -3005,7 +2983,7 @@ SelectionDisplay = (function() { } SelectionManager._update(); - };//--End of onMove def + };// End of onMove def return { mode: stretchMode, @@ -3091,15 +3069,18 @@ SelectionDisplay = (function() { } var tool = makeStretchTool(mode, direction, pivot, offset, handleMove); - addGrabberTool(overlay, tool); + return addGrabberTool(overlay, tool); } // FUNCTION: CUTOFF STRETCH FUNC function cutoffStretchFunc(vector, change) { vector = change; - Vec3.print("Radius stretch: ", vector); + var wantDebug = false; + if (wantDebug) { + Vec3.print("Radius stretch: ", vector); + } var length = vector.x + vector.y + vector.z; - var props = selectionManager.savedProperties[selectionManager.selections[0]]; + var props = SelectionManager.savedProperties[SelectionManager.selections[0]]; var radius = props.dimensions.z / 2; var originalCutoff = props.cutoff; @@ -3108,8 +3089,8 @@ SelectionDisplay = (function() { var newSize = originalSize + length; var cutoff = Math.atan2(newSize, radius) * 180 / Math.PI; - Entities.editEntity(selectionManager.selections[0], { - cutoff: cutoff, + Entities.editEntity(SelectionManager.selections[0], { + cutoff: cutoff }); SelectionManager._update(); @@ -3117,7 +3098,7 @@ SelectionDisplay = (function() { // FUNCTION: RADIUS STRETCH FUNC function radiusStretchFunc(vector, change) { - var props = selectionManager.savedProperties[selectionManager.selections[0]]; + var props = SelectionManager.savedProperties[SelectionManager.selections[0]]; // Find the axis being adjusted var size; @@ -3135,8 +3116,8 @@ SelectionDisplay = (function() { z: size }; - Entities.editEntity(selectionManager.selections[0], { - dimensions: newDimensions, + Entities.editEntity(SelectionManager.selections[0], { + dimensions: newDimensions }); SelectionManager._update(); @@ -3551,506 +3532,326 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { + var wantDebug = false; + if (wantDebug) { + print("---> updateRotationDegreesOverlay ---"); + print(" AngleFromZero: " + angleFromZero); + print(" HandleRotation - X: " + handleRotation.x + " Y: " + handleRotation.y + " Z: " + handleRotation.z); + print(" CenterPos - " + centerPosition.x + " Y: " + centerPosition.y + " Z: " + centerPosition.z); + } + var angle = angleFromZero * (Math.PI / 180); var position = { x: Math.cos(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, y: Math.sin(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, - z: 0, + z: 0 }; + if (wantDebug) { + print(" Angle: " + angle); + print(" InitialPos: " + position.x + ", " + position.y + ", " + position.z); + } + position = Vec3.multiplyQbyV(handleRotation, position); position = Vec3.sum(centerPosition, position); - Overlays.editOverlay(rotationDegreesDisplay, { + var overlayProps = { position: position, dimensions: { x: innerRadius * ROTATION_DISPLAY_SIZE_X_MULTIPLIER, y: innerRadius * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER }, lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, - text: normalizeDegrees(angleFromZero) + "°", - }); + text: normalizeDegrees(angleFromZero) + "°" + }; + if (wantDebug) { + print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); + print(" OverlayDim - X: " + overlayProps.dimensions.x + " Y: " + overlayProps.dimensions.y + " Z: " + overlayProps.dimensions.z); + print(" OverlayLineHeight: " + overlayProps.lineHeight); + print(" OverlayText: " + overlayProps.text); + } + + Overlays.editOverlay(rotationDegreesDisplay, overlayProps); + if (wantDebug) { + print("<--- updateRotationDegreesOverlay ---"); + } } + // FUNCTION DEF: updateSelectionsRotation + // Helper func used by rotation grabber tools + function updateSelectionsRotation(rotationChange) { + if (!rotationChange) { + print("ERROR: entitySelectionTool.updateSelectionsRotation - Invalid arg specified!!"); + + // EARLY EXIT + return; + } + + // Entities should only reposition if we are rotating multiple selections around + // the selections center point. Otherwise, the rotation will be around the entities + // registration point which does not need repositioning. + var reposition = (SelectionManager.selections.length > 1); + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + + var newProperties = { + rotation: Quat.multiply(rotationChange, initialProperties.rotation) + }; + + if (reposition) { + var dPos = Vec3.subtract(initialProperties.position, initialPosition); + dPos = Vec3.multiplyQbyV(rotationChange, dPos); + newProperties.position = Vec3.sum(initialPosition, dPos); + } + + Entities.editEntity(entityID, newProperties); + } + } + + function helperRotationHandleOnBegin(event, pickRay, rotAroundAxis, rotCenter, handleRotation) { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(rotation helper onBegin) -> ======================="); + } + + SelectionManager.saveProperties(); + that.setRotationHandlesVisible(false); + that.setStretchHandlesVisible(false); + that.setGrabberMoveUpVisible(false); + + initialPosition = SelectionManager.worldPosition; + rotationNormal = { x: 0, y: 0, z: 0 }; + rotationNormal[rotAroundAxis] = 1; + + // Size the overlays to the current selection size + var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(SelectionManager.worldDimensions, 0.5); + innerRadius = diagonal; + outerRadius = diagonal * 1.15; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + Overlays.editOverlay(rotateOverlayInner, { + visible: true, + rotation: handleRotation, + position: rotCenter, + size: innerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: innerAlpha + }); + + Overlays.editOverlay(rotateOverlayOuter, { + visible: true, + rotation: handleRotation, + position: rotCenter, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha + }); + + Overlays.editOverlay(rotateOverlayCurrent, { + visible: true, + rotation: handleRotation, + position: rotCenter, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9 + }); + + Overlays.editOverlay(rotationDegreesDisplay, { + visible: true + }); + + updateRotationDegreesOverlay(0, handleRotation, rotCenter); + + // editOverlays may not have committed rotation changes. + // Compute zero position based on where the overlay will be eventually. + var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); + // In case of a parallel ray, this will be null, which will cause early-out + // in the onMove helper. + rotZero = result; + + if (wantDebug) { + print("================== " + getMode() + "(rotation helper onBegin) <- ======================="); + } + }// End_Function(helperRotationHandleOnBegin) + + function helperRotationHandleOnMove(event, rotAroundAxis, rotCenter, handleRotation) { + + if (!rotZero) { + print("ERROR: entitySelectionTool.handleRotationHandleOnMove - Invalid RotationZero Specified (missed rotation target plane?)"); + + // EARLY EXIT + return; + } + + var wantDebug = false; + if (wantDebug) { + print("================== "+ getMode() + "(rotation helper onMove) -> ======================="); + Vec3.print(" rotZero: ", rotZero); + } + var pickRay = generalComputePickRay(event.x, event.y); + Overlays.editOverlay(selectionBox, { + visible: false + }); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { + visible: false + }); + + var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); + if (result) { + var centerToZero = Vec3.subtract(rotZero, rotCenter); + var centerToIntersect = Vec3.subtract(result, rotCenter); + if (wantDebug) { + Vec3.print(" RotationNormal: ", rotationNormal); + Vec3.print(" rotZero: ", rotZero); + Vec3.print(" rotCenter: ", rotCenter); + Vec3.print(" intersect: ", result); + Vec3.print(" centerToZero: ", centerToZero); + Vec3.print(" centerToIntersect: ", centerToIntersect); + } + // Note: orientedAngle which wants normalized centerToZero and centerToIntersect + // handles that internally, so it's to pass unnormalized vectors here. + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.length(centerToIntersect); + var snapToInner = distanceFromCenter < innerRadius; + var snapAngle = snapToInner ? innerSnapAngle : 1.0; + angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; + + var vec3Degrees = { x: 0, y: 0, z: 0 }; + vec3Degrees[rotAroundAxis] = angleFromZero; + var rotChange = Quat.fromVec3Degrees(vec3Degrees); + updateSelectionsRotation(rotChange); + + updateRotationDegreesOverlay(angleFromZero, handleRotation, rotCenter); + + // update the rotation display accordingly... + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var startAtRemainder = angleFromZero; + var endAtRemainder = 360; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + startAtRemainder = 0; + endAtRemainder = startAtCurrent; + } + if (snapToInner) { + Overlays.editOverlay(rotateOverlayOuter, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayInner, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: innerRadius, + majorTickMarksAngle: innerSnapAngle, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0 + }); + } else { + Overlays.editOverlay(rotateOverlayInner, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayOuter, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: outerRadius, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1 + }); + } + }// End_If(results.intersects) + + if (wantDebug) { + print("================== "+ getMode() + "(rotation helper onMove) <- ======================="); + } + }// End_Function(helperRotationHandleOnMove) + + function helperRotationHandleOnEnd() { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(onEnd) -> ======================="); + } + Overlays.editOverlay(rotateOverlayInner, { + visible: false + }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: false + }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: false + }); + Overlays.editOverlay(rotationDegreesDisplay, { + visible: false + }); + + pushCommandForSelections(); + + if (wantDebug) { + print("================== " + getMode() + "(onEnd) <- ======================="); + } + }// End_Function(helperRotationHandleOnEnd) + + // YAW GRABBER TOOL DEFINITION var initialPosition = SelectionManager.worldPosition; addGrabberTool(yawHandle, { mode: "ROTATE_YAW", - onBegin: function(event) { - SelectionManager.saveProperties(); - initialPosition = SelectionManager.worldPosition; - - // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, - }); - - updateRotationDegreesOverlay(0, yawHandleRotation, yawCenter); + onBegin: function(event, pickRay, pickResult) { + helperRotationHandleOnBegin(event, pickRay, "y", yawCenter, yawHandleRotation); }, onEnd: function(event, reason) { - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); - - pushCommandForSelections(); + helperRotationHandleOnEnd(); }, onMove: function(event) { - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); - - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - - if (result.intersects) { - var center = yawCenter; - var zero = yawZero; - var centerToZero = Vec3.subtract(zero, center); - var centerToIntersect = Vec3.subtract(result.intersection, center); - // Note: orientedAngle which wants normalized centerToZero and centerToIntersect - // handles that internally, so it's to pass unnormalized vectors here. - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - var distanceFromCenter = Vec3.distance(center, result.intersection); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - var yawChange = Quat.fromVec3Degrees({ - x: 0, - y: angleFromZero, - z: 0 - }); - - // Entities should only reposition if we are rotating multiple selections around - // the selections center point. Otherwise, the rotation will be around the entities - // registration point which does not need repositioning. - var reposition = SelectionManager.selections.length > 1; - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var properties = Entities.getEntityProperties(entityID); - var initialProperties = SelectionManager.savedProperties[entityID]; - - var newProperties = { - rotation: Quat.multiply(yawChange, initialProperties.rotation), - }; - - if (reposition) { - var dPos = Vec3.subtract(initialProperties.position, initialPosition); - dPos = Vec3.multiplyQbyV(yawChange, dPos); - newProperties.position = Vec3.sum(initialPosition, dPos); - } - - Entities.editEntity(entityID, newProperties); - } - - updateRotationDegreesOverlay(angleFromZero, yawHandleRotation, yawCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - }); - } - - } + helperRotationHandleOnMove(event, "y", yawCenter, yawHandleRotation); } }); + // PITCH GRABBER TOOL DEFINITION addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", - onBegin: function(event) { - SelectionManager.saveProperties(); - initialPosition = SelectionManager.worldPosition; - - // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, - }); - - updateRotationDegreesOverlay(0, pitchHandleRotation, pitchCenter); + onBegin: function(event, pickRay, pickResult) { + helperRotationHandleOnBegin(event, pickRay, "x", pitchCenter, pitchHandleRotation); }, onEnd: function(event, reason) { - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); - - pushCommandForSelections(); + helperRotationHandleOnEnd(); }, - onMove: function(event) { - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - - if (result.intersects) { - var center = pitchCenter; - var zero = pitchZero; - var centerToZero = Vec3.subtract(zero, center); - var centerToIntersect = Vec3.subtract(result.intersection, center); - // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles - // this internally, so it's fine to pass non-normalized versions here. - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - - var distanceFromCenter = Vec3.distance(center, result.intersection); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - - var pitchChange = Quat.fromVec3Degrees({ - x: angleFromZero, - y: 0, - z: 0 - }); - - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - var dPos = Vec3.subtract(initialProperties.position, initialPosition); - dPos = Vec3.multiplyQbyV(pitchChange, dPos); - - Entities.editEntity(entityID, { - position: Vec3.sum(initialPosition, dPos), - rotation: Quat.multiply(pitchChange, initialProperties.rotation), - }); - } - - updateRotationDegreesOverlay(angleFromZero, pitchHandleRotation, pitchCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - }); - } - } + onMove: function (event) { + helperRotationHandleOnMove(event, "x", pitchCenter, pitchHandleRotation); } }); + // ROLL GRABBER TOOL DEFINITION addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", - onBegin: function(event) { - SelectionManager.saveProperties(); - initialPosition = SelectionManager.worldPosition; - - // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, - }); - - updateRotationDegreesOverlay(0, rollHandleRotation, rollCenter); + onBegin: function(event, pickRay, pickResult) { + helperRotationHandleOnBegin(event, pickRay, "z", rollCenter, rollHandleRotation); }, - onEnd: function(event, reason) { - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); - - pushCommandForSelections(); + onEnd: function (event, reason) { + helperRotationHandleOnEnd(); }, onMove: function(event) { - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - - if (result.intersects) { - var center = rollCenter; - var zero = rollZero; - var centerToZero = Vec3.subtract(zero, center); - var centerToIntersect = Vec3.subtract(result.intersection, center); - // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles - // this internally, so it's fine to pass non-normalized versions here. - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - - var distanceFromCenter = Vec3.distance(center, result.intersection); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - - var rollChange = Quat.fromVec3Degrees({ - x: 0, - y: 0, - z: angleFromZero - }); - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - var dPos = Vec3.subtract(initialProperties.position, initialPosition); - dPos = Vec3.multiplyQbyV(rollChange, dPos); - - Entities.editEntity(entityID, { - position: Vec3.sum(initialPosition, dPos), - rotation: Quat.multiply(rollChange, initialProperties.rotation), - }); - } - - updateRotationDegreesOverlay(angleFromZero, rollHandleRotation, rollCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - }); - } - } + helperRotationHandleOnMove(event, "z", rollCenter, rollHandleRotation); } }); @@ -4059,7 +3860,7 @@ SelectionDisplay = (function() { if (SelectionManager.hasSelection()) { // FIXME - this cause problems with editing in the entity properties window - //SelectionManager._update(); + // SelectionManager._update(); if (!Vec3.equal(Camera.getPosition(), lastCameraPosition) || !Quat.equal(Camera.getOrientation(), lastCameraOrientation)) { @@ -4076,7 +3877,6 @@ SelectionDisplay = (function() { if (controllerPose.valid && lastControllerPoses[hand].valid) { if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { - print("setting controller pose"); that.mouseMoveEvent({}); } } @@ -4084,393 +3884,114 @@ SelectionDisplay = (function() { } }; - // FUNCTION: MOUSE PRESS EVENT - that.mousePressEvent = function(event) { + + // FUNCTION DEF(s): Intersection Check Helpers + function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { var wantDebug = false; - if (!event.isLeftButton && !that.triggered) { - // if another mouse button than left is pressed ignore it - return false; - } - - var somethingClicked = false; - var pickRay = generalComputePickRay(event.x, event.y); - - var result = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); - if (result.intersects) { - // mouse clicks on the tablet should override the edit affordances - return false; - } - - entityIconOverlayManager.setIconsSelectable(selectionManager.selections,true); - - // ignore ray intersection for our selection box and yaw/pitch/roll - result = Overlays.findRayIntersection(pickRay, true, null, [ yawHandle, pitchHandle, rollHandle, selectionBox ] ); - if (result.intersects) { + if ((queryRay === undefined) || (queryRay === null)) { if (wantDebug) { - print("something intersects... "); - print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); - print(" result.intersects:" + result.intersects); - print(" result.overlayID:" + result.overlayID); - print(" result.distance:" + result.distance); - print(" result.face:" + result.face); - Vec3.print(" result.intersection:", result.intersection); + print("testRayIntersect - EARLY EXIT -> queryRay is undefined OR null!"); + } + return null; + } + + var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludes, overlayExcludes); + + if (wantDebug) { + if (!overlayIncludes) { + print("testRayIntersect - no overlayIncludes provided."); + } + if (!overlayExcludes) { + print("testRayIntersect - no overlayExcludes provided."); + } + print("testRayIntersect - Hit: " + intersectObj.intersects); + print(" intersectObj.overlayID:" + intersectObj.overlayID + "[" + overlayNames[intersectObj.overlayID] + "]"); + print(" OverlayName: " + overlayNames[intersectObj.overlayID]); + print(" intersectObj.distance:" + intersectObj.distance); + print(" intersectObj.face:" + intersectObj.face); + Vec3.print(" intersectObj.intersection:", intersectObj.intersection); + } + + return intersectObj; + } + + // FUNCTION: MOUSE PRESS EVENT + that.mousePressEvent = function (event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MousePressEvent BEG ======================="); + } + if (!event.isLeftButton && !that.triggered) { + // EARLY EXIT-(if another mouse button than left is pressed ignore it) + return false; + } + + var pickRay = generalComputePickRay(event.x, event.y); + // TODO_Case6491: Move this out to setup just to make it once + var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, selectionBox]; + for (var key in grabberTools) { + if (grabberTools.hasOwnProperty(key)) { + interactiveOverlays.push(key); + } + } + + // Start with unknown mode, in case no tool can handle this. + activeTool = null; + + var results = testRayIntersect(pickRay, interactiveOverlays); + if (results.intersects) { + var hitOverlayID = results.overlayID; + if ((hitOverlayID === HMD.tabletID) || (hitOverlayID === HMD.tabletScreenID) || (hitOverlayID === HMD.homeButtonID)) { + // EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) + return false; } - var tool = grabberTools[result.overlayID]; - if (tool) { - activeTool = tool; - mode = tool.mode; - somethingClicked = 'tool'; - if (activeTool && activeTool.onBegin) { - activeTool.onBegin(event); + entityIconOverlayManager.setIconsSelectable(SelectionManager.selections, true); + + var hitTool = grabberTools[ hitOverlayID ]; + if (hitTool) { + activeTool = hitTool; + if (activeTool.onBegin) { + activeTool.onBegin(event, pickRay, results); + } else { + print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin"); } } else { - switch (result.overlayID) { - case grabberMoveUp: - mode = "TRANSLATE_UP_DOWN"; - somethingClicked = mode; + print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); + }// End_if (hitTool) + }// End_If(results.intersects) - // in translate mode, we hide our stretch handles... - for (var i = 0; i < stretchHandles.length; i++) { - Overlays.editOverlay(stretchHandles[i], { - visible: false - }); - } - break; - - - case grabberNEAR: - case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? - case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? - mode = "STRETCH_NEAR"; - somethingClicked = mode; - break; - - case grabberFAR: - case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? - case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? - mode = "STRETCH_FAR"; - somethingClicked = mode; - break; - case grabberTOP: - mode = "STRETCH_TOP"; - somethingClicked = mode; - break; - case grabberBOTTOM: - mode = "STRETCH_BOTTOM"; - somethingClicked = mode; - break; - case grabberRIGHT: - case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? - case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? - mode = "STRETCH_RIGHT"; - somethingClicked = mode; - break; - case grabberLEFT: - case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? - case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? - mode = "STRETCH_LEFT"; - somethingClicked = mode; - break; - - default: - mode = "UNKNOWN"; - break; - } - } + if (wantDebug) { + print(" DisplayMode: " + getMode()); + print("=============== eST::MousePressEvent END ======================="); } - // if one of the items above was clicked, then we know we are in translate or stretch mode, and we - // should hide our rotate handles... - if (somethingClicked) { - Overlays.editOverlay(yawHandle, { - visible: false - }); - Overlays.editOverlay(pitchHandle, { - visible: false - }); - Overlays.editOverlay(rollHandle, { - visible: false - }); - - if (mode != "TRANSLATE_UP_DOWN") { - Overlays.editOverlay(grabberMoveUp, { - visible: false - }); - } - } - - if (!somethingClicked) { - - if (wantDebug) { - print("rotate handle case..."); - } - - - // Only intersect versus yaw/pitch/roll. - result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); - - var overlayOrientation; - var overlayCenter; - - var properties = Entities.getEntityProperties(selectionManager.selections[0]); - var angles = Quat.safeEulerAngles(properties.rotation); - var pitch = angles.x; - var yaw = angles.y; - var roll = angles.z; - - originalRotation = properties.rotation; - originalPitch = pitch; - originalYaw = yaw; - originalRoll = roll; - - if (result.intersects) { - var resultTool = grabberTools[result.overlayID]; - if (resultTool) { - activeTool = resultTool; - mode = resultTool.mode; - somethingClicked = 'tool'; - if (activeTool && activeTool.onBegin) { - activeTool.onBegin(event); - } - } - switch (result.overlayID) { - case yawHandle: - mode = "ROTATE_YAW"; - somethingClicked = mode; - overlayOrientation = yawHandleRotation; - overlayCenter = yawCenter; - yawZero = result.intersection; - rotationNormal = yawNormal; - break; - - case pitchHandle: - mode = "ROTATE_PITCH"; - initialPosition = SelectionManager.worldPosition; - somethingClicked = mode; - overlayOrientation = pitchHandleRotation; - overlayCenter = pitchCenter; - pitchZero = result.intersection; - rotationNormal = pitchNormal; - break; - - case rollHandle: - mode = "ROTATE_ROLL"; - somethingClicked = mode; - overlayOrientation = rollHandleRotation; - overlayCenter = rollCenter; - rollZero = result.intersection; - rotationNormal = rollNormal; - break; - - default: - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - } - mode = "UNKNOWN"; - break; - } - } - if (wantDebug) { - print(" somethingClicked:" + somethingClicked); - print(" mode:" + mode); - } - - if (somethingClicked) { - - Overlays.editOverlay(rotateOverlayTarget, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter - }); - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter, - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter, - startAt: 0, - endAt: 0 - }); - Overlays.editOverlay(yawHandle, { - visible: false - }); - Overlays.editOverlay(pitchHandle, { - visible: false - }); - Overlays.editOverlay(rollHandle, { - visible: false - }); - - - // TODO: these three duplicate prior three, remove them. - Overlays.editOverlay(yawHandle, { - visible: false - }); - Overlays.editOverlay(pitchHandle, { - visible: false - }); - Overlays.editOverlay(rollHandle, { - visible: false - }); - Overlays.editOverlay(grabberMoveUp, { - visible: false - }); - Overlays.editOverlay(grabberLBN, { - visible: false - }); - Overlays.editOverlay(grabberLBF, { - visible: false - }); - Overlays.editOverlay(grabberRBN, { - visible: false - }); - Overlays.editOverlay(grabberRBF, { - visible: false - }); - Overlays.editOverlay(grabberLTN, { - visible: false - }); - Overlays.editOverlay(grabberLTF, { - visible: false - }); - Overlays.editOverlay(grabberRTN, { - visible: false - }); - Overlays.editOverlay(grabberRTF, { - visible: false - }); - - Overlays.editOverlay(grabberTOP, { - visible: false - }); - Overlays.editOverlay(grabberBOTTOM, { - visible: false - }); - Overlays.editOverlay(grabberLEFT, { - visible: false - }); - Overlays.editOverlay(grabberRIGHT, { - visible: false - }); - Overlays.editOverlay(grabberNEAR, { - visible: false - }); - Overlays.editOverlay(grabberFAR, { - visible: false - }); - - Overlays.editOverlay(grabberEdgeTR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeTL, { - visible: false - }); - Overlays.editOverlay(grabberEdgeTF, { - visible: false - }); - Overlays.editOverlay(grabberEdgeTN, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBL, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBF, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBN, { - visible: false - }); - Overlays.editOverlay(grabberEdgeNR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeNL, { - visible: false - }); - Overlays.editOverlay(grabberEdgeFR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeFL, { - visible: false - }); - } - } - - if (!somethingClicked) { - // Only intersect versus selectionBox. - result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); - if (result.intersects) { - switch (result.overlayID) { - case selectionBox: - activeTool = translateXZTool; - translateXZTool.pickPlanePosition = result.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), - SelectionManager.worldDimensions.z); - if (wantDebug) { - print("longest dimension: " + translateXZTool.greatestDimension); - translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); - print("starting distance: " + translateXZTool.startingDistance); - translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); - print(" starting elevation: " + translateXZTool.startingElevation); - } - - mode = translateXZTool.mode; - activeTool.onBegin(event); - somethingClicked = 'selectionBox'; - break; - default: - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - } - mode = "UNKNOWN"; - break; - } - } - } - - if (somethingClicked) { - pickRay = generalComputePickRay(event.x, event.y); - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - } - } - - // reset everything as intersectable... - // TODO: we could optimize this since some of these were already flipped back - Overlays.editOverlay(selectionBox, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(yawHandle, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(pitchHandle, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(rollHandle, { - ignoreRayIntersection: false - }); - - return somethingClicked; + // If mode is known then we successfully handled this; + // otherwise, we're missing a tool. + return activeTool; }; // FUNCTION: MOUSE MOVE EVENT that.mouseMoveEvent = function(event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseMoveEvent BEG ======================="); + } if (activeTool) { + if (wantDebug) { + print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); + } activeTool.onMove(event); + + if (wantDebug) { + print(" Trigger SelectionManager::update"); + } SelectionManager._update(); + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } + // EARLY EXIT--(Move handled via active tool) return true; } @@ -4554,7 +4075,7 @@ SelectionDisplay = (function() { pickedAlpha = grabberAlpha; highlightNeeded = true; break; - + default: if (previousHandle) { Overlays.editOverlay(previousHandle, { @@ -4593,60 +4114,35 @@ SelectionDisplay = (function() { } } + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } return false; }; - // FUNCTION: UPDATE HANDLE SIZES - that.updateHandleSizes = function() { - if (selectionManager.hasSelection()) { - var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); - var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; - var dimensions = SelectionManager.worldDimensions; - var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; - grabberSize = Math.min(grabberSize, avgDimension / 10); - - for (var i = 0; i < stretchHandles.length; i++) { - Overlays.editOverlay(stretchHandles[i], { - size: grabberSize, - }); - } - var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7; - handleSize = Math.min(handleSize, avgDimension / 3); - - Overlays.editOverlay(yawHandle, { - scale: handleSize, - }); - Overlays.editOverlay(pitchHandle, { - scale: handleSize, - }); - Overlays.editOverlay(rollHandle, { - scale: handleSize, - }); - var pos = Vec3.sum(grabberMoveUpPosition, { - x: 0, - y: Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3, - z: 0 - }); - Overlays.editOverlay(grabberMoveUp, { - position: pos, - scale: handleSize / 1.25, - }); - } - }; - Script.update.connect(that.updateHandleSizes); - // FUNCTION: MOUSE RELEASE EVENT that.mouseReleaseEvent = function(event) { - var showHandles = false; - if (activeTool && activeTool.onEnd) { - activeTool.onEnd(event); + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseReleaseEvent BEG ======================="); } - activeTool = null; + var showHandles = false; + if (activeTool) { + if (activeTool.onEnd) { + if (wantDebug) { + print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd"); + } + activeTool.onEnd(event); + } else if (wantDebug) { + print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); + } + } + // hide our rotation overlays..., and show our handles - if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL") { - Overlays.editOverlay(rotateOverlayTarget, { - visible: false - }); + if (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)) { + if (wantDebug) { + print(" Triggering hide of RotateOverlays"); + } Overlays.editOverlay(rotateOverlayInner, { visible: false }); @@ -4656,22 +4152,25 @@ SelectionDisplay = (function() { Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); - showHandles = true; + } - if (mode != "UNKNOWN") { - showHandles = true; - } - - mode = "UNKNOWN"; + showHandles = activeTool; // base on prior tool value + activeTool = null; // if something is selected, then reset the "original" properties for any potential next click+move operation if (SelectionManager.hasSelection()) { if (showHandles) { + if (wantDebug) { + print(" Triggering that.select"); + } that.select(SelectionManager.selections[0], event); } } + if (wantDebug) { + print("=============== eST::MouseReleaseEvent END ======================="); + } }; // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: @@ -4680,7 +4179,6 @@ SelectionDisplay = (function() { Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); - return that; }()); diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index 351f10e7bd..058910940b 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -370,7 +370,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit return Math.min(Math.max(value, min), max); } - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height }; that.windowDimensions = recommendedDimmensions; // Controller.getViewportDimensions(); that.origin = { x: recommendedRect.x, y: recommendedRect.y }; @@ -378,7 +378,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit // For example, maybe we want "keep the same percentage to whatever two edges are closest to the edge of screen". // If we change that, the places to do so are onResizeViewport, save (maybe), and the initial move based on Settings, below. that.onResizeViewport = function (newSize) { // Can be overridden or extended by clients. - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height }; var originRelativeX = (that.x - that.origin.x - that.offset.x); var originRelativeY = (that.y - that.origin.y - that.offset.y); @@ -396,7 +396,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit // code once the new toolbar position is well established with users. this.isNewPositionKey = optionalPersistenceKey + '.isNewPosition'; this.save = function () { - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var screenSize = { x: recommendedRect.width, y: recommendedRect.height }; if (screenSize.x > 0 && screenSize.y > 0) { // Guard against invalid screen size that can occur at shut-down. @@ -443,7 +443,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit that.move(that.dragOffsetX + event.x, that.dragOffsetY + event.y); }; that.checkResize = function () { // Can be overriden or extended, but usually not. See onResizeViewport. - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var currentWindowSize = { x: recommendedRect.width, y: recommendedRect.height }; if ((currentWindowSize.x !== that.windowDimensions.x) || (currentWindowSize.y !== that.windowDimensions.y)) { @@ -471,7 +471,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit var savedFraction = isNewPosition ? JSON.parse(Settings.getValue(this.fractionKey) || "0") : 0; Settings.setValue(this.isNewPositionKey, true); - var recommendedRect = Controller.getRecommendedOverlayRect(); + var recommendedRect = Controller.getRecommendedHUDRect(); var screenSize = { x: recommendedRect.width, y: recommendedRect.height }; if (savedFraction) { // If we have saved data, keep the toolbar at the same proportion of the screen width/height. diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 162edcaea0..e6730b8826 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -6,8 +6,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// note: this constant is currently duplicated in edit.js -EDIT_SETTING = "io.highfidelity.isEditting"; +// note: this constant is currently duplicated in edit.js and ambientSound.js +EDIT_SETTING = "io.highfidelity.isEditing"; isInEditMode = function isInEditMode() { return Settings.getValue(EDIT_SETTING); }; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index e94b227a4a..6880d10c18 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -98,6 +98,7 @@ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { + Wallet.refreshWalletStatus(); var entity = HMD.tabletID; Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); showMarketplace(); @@ -129,22 +130,16 @@ } } - function setCertificateInfo(currentEntityWithContextOverlay, itemMarketplaceId) { + function openWallet() { + tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + } + + function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) { wireEventBridge(true); tablet.sendToQml({ - method: 'inspectionCertificate_setMarketplaceId', - marketplaceId: itemMarketplaceId || Entities.getEntityProperties(currentEntityWithContextOverlay, ['marketplaceID']).marketplaceID + method: 'inspectionCertificate_setCertificateId', + certificateId: itemCertificateId || Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID }); - // ZRF FIXME! Make a call to the endpoint to get item info instead of this silliness - Script.setTimeout(function () { - var randomNumber = Math.floor((Math.random() * 150) + 1); - tablet.sendToQml({ - method: 'inspectionCertificate_setItemInfo', - itemName: "The Greatest Item", - itemOwner: "ABCDEFG1234567", - itemEdition: (Math.floor(Math.random() * randomNumber) + " / " + randomNumber) - }); - }, 500); } function onUsernameChanged() { @@ -153,11 +148,25 @@ } } + function sendCommerceSettings() { + tablet.emitScriptEvent(JSON.stringify({ + type: "marketplaces", + action: "commerceSetting", + data: { + commerceMode: Settings.getValue("commerce", false), + userIsLoggedIn: Account.loggedIn, + walletNeedsSetup: Wallet.walletStatus === 1 + } + })); + } + marketplaceButton.clicked.connect(onClick); tablet.screenChanged.connect(onScreenChanged); Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); GlobalServices.myUsernameChanged.connect(onUsernameChanged); + Wallet.walletStatusChanged.connect(sendCommerceSettings); + Wallet.refreshWalletStatus(); function onMessage(message) { @@ -198,15 +207,7 @@ canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified }); } else if (parsedJsonMessage.type === "REQUEST_SETTING") { - tablet.emitScriptEvent(JSON.stringify({ - type: "marketplaces", - action: "commerceSetting", - data: { - commerceMode: Settings.getValue("commerce", false), - userIsLoggedIn: Account.loggedIn, - walletNeedsSetup: Wallet.walletStatus === 1 - } - })); + sendCommerceSettings(); } else if (parsedJsonMessage.type === "PURCHASES") { referrerURL = parsedJsonMessage.referrerURL; filterText = ""; @@ -214,7 +215,7 @@ } else if (parsedJsonMessage.type === "LOGIN") { openLoginWindow(); } else if (parsedJsonMessage.type === "WALLET_SETUP") { - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + openWallet(); } else if (parsedJsonMessage.type === "MY_ITEMS") { referrerURL = MARKETPLACE_URL_INITIAL; filterText = ""; @@ -239,6 +240,7 @@ tablet.webEventReceived.disconnect(onMessage); Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + Wallet.walletStatusChanged.disconnect(sendCommerceSettings); }); @@ -281,16 +283,22 @@ case 'purchases_openWallet': case 'checkout_openWallet': case 'checkout_setUpClicked': - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + openWallet(); break; case 'purchases_walletNotSetUp': - case 'checkout_walletNotSetUp': wireEventBridge(true); tablet.sendToQml({ method: 'updateWalletReferrer', referrer: "purchases" }); - tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + openWallet(); + case 'checkout_walletNotSetUp': + wireEventBridge(true); + tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: message.itemId + }); + openWallet(); break; case 'checkout_cancelClicked': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); @@ -340,13 +348,13 @@ tablet.loadQMLSource("TabletAddressDialog.qml"); break; case 'purchases_itemCertificateClicked': - setCertificateInfo("", message.itemMarketplaceId); + setCertificateInfo("", message.itemCertificateId); break; case 'inspectionCertificate_closeClicked': tablet.gotoHomeScreen(); break; case 'inspectionCertificate_showInMarketplaceClicked': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL); break; case 'header_myItemsClicked': referrerURL = MARKETPLACE_URL_INITIAL; diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index ffe93d13e8..ef2c674a41 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -85,6 +85,7 @@ var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; var lodTextID = false; + var NOTIFICATIONS_MESSAGE_CHANNEL = "Hifi-Notifications" var NotificationType = { UNKNOWN: 0, @@ -94,13 +95,15 @@ EDIT_ERROR: 4, TABLET: 5, CONNECTION: 6, + WALLET: 7, properties: [ { text: "Snapshot" }, { text: "Level of Detail" }, { text: "Connection Refused" }, { text: "Edit error" }, { text: "Tablet" }, - { text: "Connection" } + { text: "Connection" }, + { text: "Wallet" } ], getTypeFromMenuItem: function (menuItemName) { var type; @@ -548,6 +551,13 @@ createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this } + function onMessageReceived(channel, message) { + if (channel === NOTIFICATIONS_MESSAGE_CHANNEL) { + message = JSON.parse(message); + createNotification(wordWrap(message.message), message.notificationType); + } + } + function onSnapshotTaken(pathStillSnapshot, notify) { if (notify) { var imageProperties = { @@ -566,6 +576,10 @@ createNotification("Processing GIF snapshot...", NotificationType.SNAPSHOT); } + function processingGif() { + createNotification("Your wallet isn't set up. Open the WALLET app.", NotificationType.WALLET); + } + function connectionAdded(connectionName) { createNotification(connectionName, NotificationType.CONNECTION); } @@ -640,6 +654,7 @@ Overlays.deleteOverlay(buttons[notificationIndex]); } Menu.removeMenu(MENU_NAME); + Messages.unsubscribe(NOTIFICATIONS_MESSAGE_CHANNEL); } function menuItemEvent(menuItem) { @@ -682,6 +697,11 @@ Window.notifyEditError = onEditError; Window.notify = onNotify; Tablet.tabletNotification.connect(tabletNotification); + Wallet.walletNotSetup.connect(walletNotSetup); + + Messages.subscribe(NOTIFICATIONS_MESSAGE_CHANNEL); + Messages.messageReceived.connect(onMessageReceived); + setup(); }()); // END LOCAL_SCOPE diff --git a/scripts/tutorials/entity_scripts/ambientSound.js b/scripts/tutorials/entity_scripts/ambientSound.js index e0a7d0a3cf..dd964fb4e0 100644 --- a/scripts/tutorials/entity_scripts/ambientSound.js +++ b/scripts/tutorials/entity_scripts/ambientSound.js @@ -139,7 +139,7 @@ this._toggle = function(hint) { // Toggle between ON/OFF state, but only if not in edit mode - if (Settings.getValue("io.highfidelity.isEditting")) { + if (Settings.getValue("io.highfidelity.isEditing")) { return; } var props = Entities.getEntityProperties(entity, [ "userData" ]); diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 5de44e8897..cf11ef9e7a 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,23 +2,25 @@ add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") -add_subdirectory(udt-test) -set_target_properties(udt-test PROPERTIES FOLDER "Tools") +if (BUILD_TOOLS) + add_subdirectory(udt-test) + set_target_properties(udt-test PROPERTIES FOLDER "Tools") -add_subdirectory(vhacd-util) -set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") + add_subdirectory(vhacd-util) + set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") -add_subdirectory(ice-client) -set_target_properties(ice-client PROPERTIES FOLDER "Tools") + add_subdirectory(ice-client) + set_target_properties(ice-client PROPERTIES FOLDER "Tools") -add_subdirectory(ac-client) -set_target_properties(ac-client PROPERTIES FOLDER "Tools") + add_subdirectory(ac-client) + set_target_properties(ac-client PROPERTIES FOLDER "Tools") -add_subdirectory(skeleton-dump) -set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools") + add_subdirectory(skeleton-dump) + set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools") -add_subdirectory(atp-client) -set_target_properties(atp-client PROPERTIES FOLDER "Tools") + add_subdirectory(atp-client) + set_target_properties(atp-client PROPERTIES FOLDER "Tools") -add_subdirectory(oven) -set_target_properties(oven PROPERTIES FOLDER "Tools") + add_subdirectory(oven) + set_target_properties(oven PROPERTIES FOLDER "Tools") +endif() diff --git a/unpublishedScripts/marketplace/shapes/assets/audio/clone.wav b/unpublishedScripts/marketplace/shapes/assets/audio/clone.wav new file mode 100644 index 0000000000..2a0e21cdd2 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/audio/clone.wav differ diff --git a/unpublishedScripts/marketplace/shapes/assets/audio/create.wav b/unpublishedScripts/marketplace/shapes/assets/audio/create.wav new file mode 100644 index 0000000000..9571c5cff7 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/audio/create.wav differ diff --git a/unpublishedScripts/marketplace/shapes/assets/audio/delete.wav b/unpublishedScripts/marketplace/shapes/assets/audio/delete.wav new file mode 100644 index 0000000000..da93ca7e10 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/audio/delete.wav differ diff --git a/unpublishedScripts/marketplace/shapes/assets/audio/drop.wav b/unpublishedScripts/marketplace/shapes/assets/audio/drop.wav new file mode 100644 index 0000000000..32d0e9f65a Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/audio/drop.wav differ diff --git a/unpublishedScripts/marketplace/shapes/assets/audio/equip.wav b/unpublishedScripts/marketplace/shapes/assets/audio/equip.wav new file mode 100644 index 0000000000..32d0e9f65a Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/audio/equip.wav differ diff --git a/unpublishedScripts/marketplace/shapes/assets/audio/error.wav b/unpublishedScripts/marketplace/shapes/assets/audio/error.wav new file mode 100644 index 0000000000..934a4d366b Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/audio/error.wav differ diff --git a/unpublishedScripts/marketplace/shapes/assets/audio/select.wav b/unpublishedScripts/marketplace/shapes/assets/audio/select.wav new file mode 100644 index 0000000000..e97b5ea6d7 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/audio/select.wav differ diff --git a/unpublishedScripts/marketplace/shapes/assets/blue-header-bar.fbx b/unpublishedScripts/marketplace/shapes/assets/blue-header-bar.fbx new file mode 100644 index 0000000000..951a6667e7 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/blue-header-bar.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/circle.fbx b/unpublishedScripts/marketplace/shapes/assets/create/circle.fbx new file mode 100644 index 0000000000..db43fafab6 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/circle.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/cone.fbx b/unpublishedScripts/marketplace/shapes/assets/create/cone.fbx new file mode 100644 index 0000000000..d590ee04be Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/cone.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/create-heading.svg b/unpublishedScripts/marketplace/shapes/assets/create/create-heading.svg new file mode 100644 index 0000000000..fa2a096ede --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/create/create-heading.svg @@ -0,0 +1,12 @@ + + + + CREATE + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/create/cube.fbx b/unpublishedScripts/marketplace/shapes/assets/create/cube.fbx new file mode 100644 index 0000000000..fa079219fe Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/cube.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/cylinder.fbx b/unpublishedScripts/marketplace/shapes/assets/create/cylinder.fbx new file mode 100644 index 0000000000..319cbf0a6d Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/cylinder.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/dodecahedron.fbx b/unpublishedScripts/marketplace/shapes/assets/create/dodecahedron.fbx new file mode 100644 index 0000000000..af2b7f3f55 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/dodecahedron.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/hexagon.fbx b/unpublishedScripts/marketplace/shapes/assets/create/hexagon.fbx new file mode 100644 index 0000000000..ffb63fb64d Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/hexagon.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/icosahedron.fbx b/unpublishedScripts/marketplace/shapes/assets/create/icosahedron.fbx new file mode 100644 index 0000000000..3103d6b2d6 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/icosahedron.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/octagon.fbx b/unpublishedScripts/marketplace/shapes/assets/create/octagon.fbx new file mode 100644 index 0000000000..8de0f6f6f3 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/octagon.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/octahedron.fbx b/unpublishedScripts/marketplace/shapes/assets/create/octahedron.fbx new file mode 100644 index 0000000000..6798566d3b Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/octahedron.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/prism.fbx b/unpublishedScripts/marketplace/shapes/assets/create/prism.fbx new file mode 100644 index 0000000000..f1ddc4933e Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/prism.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/sphere.fbx b/unpublishedScripts/marketplace/shapes/assets/create/sphere.fbx new file mode 100644 index 0000000000..43b077b225 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/sphere.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/create/tetrahedron.fbx b/unpublishedScripts/marketplace/shapes/assets/create/tetrahedron.fbx new file mode 100644 index 0000000000..60dd70edb1 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/create/tetrahedron.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/gray-header.fbx b/unpublishedScripts/marketplace/shapes/assets/gray-header.fbx new file mode 100644 index 0000000000..0f51d66f38 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/gray-header.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/green-header-bar.fbx b/unpublishedScripts/marketplace/shapes/assets/green-header-bar.fbx new file mode 100644 index 0000000000..cf76830d8c Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/green-header-bar.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/green-header.fbx b/unpublishedScripts/marketplace/shapes/assets/green-header.fbx new file mode 100644 index 0000000000..db77ecf0bd Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/green-header.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/horizontal-rule.svg b/unpublishedScripts/marketplace/shapes/assets/horizontal-rule.svg new file mode 100644 index 0000000000..d7dc41f719 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/horizontal-rule.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/shapes-a.svg b/unpublishedScripts/marketplace/shapes/assets/shapes-a.svg new file mode 100644 index 0000000000..07918ba294 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/shapes-a.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/shapes-d.svg b/unpublishedScripts/marketplace/shapes/assets/shapes-d.svg new file mode 100644 index 0000000000..fa64b519b9 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/shapes-d.svg @@ -0,0 +1,18 @@ + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/shapes-i.svg b/unpublishedScripts/marketplace/shapes/assets/shapes-i.svg new file mode 100644 index 0000000000..cc9df9e64a --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/shapes-i.svg @@ -0,0 +1,18 @@ + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/back-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/back-heading.svg new file mode 100644 index 0000000000..d70f315ea1 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/back-heading.svg @@ -0,0 +1,12 @@ + + + + BACK + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/back-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/back-icon.svg new file mode 100644 index 0000000000..7de1781804 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/back-icon.svg @@ -0,0 +1,14 @@ + + + + back-icon + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/clone-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/clone-icon.svg new file mode 100644 index 0000000000..324c7d57ba --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/clone-icon.svg @@ -0,0 +1,14 @@ + + + + clone-icon + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/clone-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/clone-label.svg new file mode 100644 index 0000000000..1a141714e8 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/clone-label.svg @@ -0,0 +1,12 @@ + + + + CLONE + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/clone-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/clone-tool-heading.svg new file mode 100644 index 0000000000..6ab57cd0e1 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/clone-tool-heading.svg @@ -0,0 +1,12 @@ + + + + CLONE TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/color-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color-icon.svg new file mode 100644 index 0000000000..9363b7607f --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/color-icon.svg @@ -0,0 +1,15 @@ + + + + color-icon + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/color-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color-label.svg new file mode 100644 index 0000000000..008b7b963d --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/color-label.svg @@ -0,0 +1,12 @@ + + + + COLOR + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/color-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color-tool-heading.svg new file mode 100644 index 0000000000..5b1979e776 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/color-tool-heading.svg @@ -0,0 +1,12 @@ + + + + COLOR TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle-black.png b/unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle-black.png new file mode 100644 index 0000000000..4b62c28a4d Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle-black.png differ diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle.png b/unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle.png new file mode 100644 index 0000000000..8bf2613382 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/tools/color/color-circle.png differ diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/color/pick-color-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color/pick-color-label.svg new file mode 100644 index 0000000000..6fa2997328 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/color/pick-color-label.svg @@ -0,0 +1,14 @@ + + + + noun_792623 + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/color/slider-alpha.png b/unpublishedScripts/marketplace/shapes/assets/tools/color/slider-alpha.png new file mode 100644 index 0000000000..e7693935b2 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/tools/color/slider-alpha.png differ diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/color/slider-white.png b/unpublishedScripts/marketplace/shapes/assets/tools/color/slider-white.png new file mode 100644 index 0000000000..048936b106 Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/tools/color/slider-white.png differ diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/color/swatches-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/color/swatches-label.svg new file mode 100644 index 0000000000..76c9d6228f --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/color/swatches-label.svg @@ -0,0 +1,12 @@ + + + + SWATCHES + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/common/actions-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/actions-label.svg new file mode 100644 index 0000000000..d5428ae3f1 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/common/actions-label.svg @@ -0,0 +1,12 @@ + + + + ACTIONS + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/common/down-arrow.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/down-arrow.svg new file mode 100644 index 0000000000..352b830aad --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/common/down-arrow.svg @@ -0,0 +1,12 @@ + + + + 5 + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/common/finish-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/finish-label.svg new file mode 100644 index 0000000000..58120a337a --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/common/finish-label.svg @@ -0,0 +1,12 @@ + + + + FINISH + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/common/info-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/info-icon.svg new file mode 100644 index 0000000000..ef2495b728 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/common/info-icon.svg @@ -0,0 +1,12 @@ + + + + [ + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/common/up-arrow.svg b/unpublishedScripts/marketplace/shapes/assets/tools/common/up-arrow.svg new file mode 100644 index 0000000000..da80a4e9ce --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/common/up-arrow.svg @@ -0,0 +1,12 @@ + + + + 5 copy + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/delete-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/delete-icon.svg new file mode 100644 index 0000000000..f77d40f1e6 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/delete-icon.svg @@ -0,0 +1,14 @@ + + + + delete-icon + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/delete-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/delete-label.svg new file mode 100644 index 0000000000..e63000e209 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/delete-label.svg @@ -0,0 +1,12 @@ + + + + DELETE + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/delete-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/delete-tool-heading.svg new file mode 100644 index 0000000000..e92e3c1d00 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/delete-tool-heading.svg @@ -0,0 +1,12 @@ + + + + DELETE TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/delete/info-text.svg b/unpublishedScripts/marketplace/shapes/assets/tools/delete/info-text.svg new file mode 100644 index 0000000000..4148bd8525 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/delete/info-text.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/group-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group-icon.svg new file mode 100644 index 0000000000..56abd0a30c --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/group-icon.svg @@ -0,0 +1,33 @@ + + + + group-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/group-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group-label.svg new file mode 100644 index 0000000000..001ce5b953 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/group-label.svg @@ -0,0 +1,12 @@ + + + + GROUP + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/group-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group-tool-heading.svg new file mode 100644 index 0000000000..e1942213e2 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/group-tool-heading.svg @@ -0,0 +1,12 @@ + + + + GROUP TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/group/clear-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group/clear-label.svg new file mode 100644 index 0000000000..8bc5daa8b4 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/group/clear-label.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/group/group-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group/group-label.svg new file mode 100644 index 0000000000..b2f15b4b22 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/group/group-label.svg @@ -0,0 +1,12 @@ + + + + GROUP + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/group/selection-box-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group/selection-box-label.svg new file mode 100644 index 0000000000..21bed98a90 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/group/selection-box-label.svg @@ -0,0 +1,33 @@ + + + + group-icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/group/ungroup-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/group/ungroup-label.svg new file mode 100644 index 0000000000..ec246359b5 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/group/ungroup-label.svg @@ -0,0 +1,12 @@ + + + + UNGROUP + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics-icon.svg new file mode 100644 index 0000000000..ef2635c312 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics-icon.svg @@ -0,0 +1,15 @@ + + + + physics-icon + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics-label.svg new file mode 100644 index 0000000000..27006f62b8 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics-label.svg @@ -0,0 +1,12 @@ + + + + PHYSICS + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics-tool-heading.svg new file mode 100644 index 0000000000..fb5d696111 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics-tool-heading.svg @@ -0,0 +1,12 @@ + + + + PHYSICS TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/collisions-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/collisions-label.svg new file mode 100644 index 0000000000..ff3cb0dff3 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/collisions-label.svg @@ -0,0 +1,12 @@ + + + + COLLISIONS + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/grabbable-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/grabbable-label.svg new file mode 100644 index 0000000000..5b1b797540 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/grabbable-label.svg @@ -0,0 +1,12 @@ + + + + GRABBABLE + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/gravity-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/gravity-label.svg new file mode 100644 index 0000000000..6c92a792ef --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/gravity-label.svg @@ -0,0 +1,12 @@ + + + + GRAVITY Copy + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/off-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/off-label.svg new file mode 100644 index 0000000000..0299aa9864 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/off-label.svg @@ -0,0 +1,12 @@ + + + + OFF + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/on-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/on-label.svg new file mode 100644 index 0000000000..a17a5091ad --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/buttons/on-label.svg @@ -0,0 +1,12 @@ + + + + ON + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets-label.svg new file mode 100644 index 0000000000..abe9c5c084 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets-label.svg @@ -0,0 +1,12 @@ + + + + PRESETS + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/balloon-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/balloon-label.svg new file mode 100644 index 0000000000..920e5f3c51 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/balloon-label.svg @@ -0,0 +1,12 @@ + + + + BALLOON + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/cotton-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/cotton-label.svg new file mode 100644 index 0000000000..abc77ae8f3 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/cotton-label.svg @@ -0,0 +1,12 @@ + + + + COTTON + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/custom-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/custom-label.svg new file mode 100644 index 0000000000..7081e5b35d --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/custom-label.svg @@ -0,0 +1,12 @@ + + + + CUSTOM … + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/default-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/default-label.svg new file mode 100644 index 0000000000..24b053750d --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/default-label.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/ice-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/ice-label.svg new file mode 100644 index 0000000000..b919283e9f --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/ice-label.svg @@ -0,0 +1,12 @@ + + + + ICE + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/lead-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/lead-label.svg new file mode 100644 index 0000000000..a2119fb8f0 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/lead-label.svg @@ -0,0 +1,12 @@ + + + + LEAD Copy + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/rubber-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/rubber-label.svg new file mode 100644 index 0000000000..5b4ed55053 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/rubber-label.svg @@ -0,0 +1,12 @@ + + + + RUBBER + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/tumbleweed-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/tumbleweed-label.svg new file mode 100644 index 0000000000..ecd824257e --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/tumbleweed-label.svg @@ -0,0 +1,12 @@ + + + + TUMBLEWEED + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/wood-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/wood-label.svg new file mode 100644 index 0000000000..59c348fd52 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/wood-label.svg @@ -0,0 +1,12 @@ + + + + WOOD + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/zero-g-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/zero-g-label.svg new file mode 100644 index 0000000000..a1a349b8c5 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/presets/zero-g-label.svg @@ -0,0 +1,12 @@ + + + + ZERO-G + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/properties-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/properties-label.svg new file mode 100644 index 0000000000..25198edb62 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/properties-label.svg @@ -0,0 +1,12 @@ + + + + PROPERTIES + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/bounce-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/bounce-label.svg new file mode 100644 index 0000000000..a997fa80a2 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/bounce-label.svg @@ -0,0 +1,12 @@ + + + + BOUNCE + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/density-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/density-label.svg new file mode 100644 index 0000000000..ec5fd5cf98 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/density-label.svg @@ -0,0 +1,12 @@ + + + + DENSITY + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/friction-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/friction-label.svg new file mode 100644 index 0000000000..2ff20a71cb --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/friction-label.svg @@ -0,0 +1,12 @@ + + + + FRICTION + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/gravity-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/gravity-label.svg new file mode 100644 index 0000000000..692a4ebf79 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/physics/sliders/gravity-label.svg @@ -0,0 +1,12 @@ + + + + GRAVITY + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/redo-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/redo-icon.svg new file mode 100644 index 0000000000..bd0cea1330 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/redo-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/redo-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/redo-label.svg new file mode 100644 index 0000000000..19ae558bb9 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/redo-label.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/stretch-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/stretch-icon.svg new file mode 100644 index 0000000000..dc0547b813 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/stretch-icon.svg @@ -0,0 +1,14 @@ + + + + stretch-icon + Created with Sketch. + + + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/stretch-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/stretch-label.svg new file mode 100644 index 0000000000..0aecd01a84 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/stretch-label.svg @@ -0,0 +1,12 @@ + + + + STRETCH + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/stretch-tool-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/stretch-tool-heading.svg new file mode 100644 index 0000000000..0d3fde298c --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/stretch-tool-heading.svg @@ -0,0 +1,12 @@ + + + + STRETCH TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/stretch/info-text.svg b/unpublishedScripts/marketplace/shapes/assets/tools/stretch/info-text.svg new file mode 100644 index 0000000000..4bd23f7b7f --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/stretch/info-text.svg @@ -0,0 +1,12 @@ + + + + Stretch objects by g + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/tool-icon.fbx b/unpublishedScripts/marketplace/shapes/assets/tools/tool-icon.fbx new file mode 100644 index 0000000000..b4a21c523b Binary files /dev/null and b/unpublishedScripts/marketplace/shapes/assets/tools/tool-icon.fbx differ diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/tool-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/tool-label.svg new file mode 100644 index 0000000000..bc8b059bdb --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/tool-label.svg @@ -0,0 +1,12 @@ + + + + TOOL + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/tools-heading.svg b/unpublishedScripts/marketplace/shapes/assets/tools/tools-heading.svg new file mode 100644 index 0000000000..e180ae7251 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/tools-heading.svg @@ -0,0 +1,12 @@ + + + + TOOLS + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/undo-icon.svg b/unpublishedScripts/marketplace/shapes/assets/tools/undo-icon.svg new file mode 100644 index 0000000000..566de28906 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/undo-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/assets/tools/undo-label.svg b/unpublishedScripts/marketplace/shapes/assets/tools/undo-label.svg new file mode 100644 index 0000000000..ca749f2765 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/assets/tools/undo-label.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/shapes/modules/createPalette.js b/unpublishedScripts/marketplace/shapes/modules/createPalette.js new file mode 100644 index 0000000000..0eea8379d6 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/createPalette.js @@ -0,0 +1,546 @@ +// +// createPalette.js +// +// Created by David Rowe on 28 Jul 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 +// + +/* global CreatePalette: true, App, Feedback, History, UIT */ + +CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) { + // Tool menu displayed on top of forearm. + + "use strict"; + + var paletteOriginOverlay, + paletteHeaderHeadingOverlay, + paletteHeaderBarOverlay, + paletteTitleOverlay, + palettePanelOverlay, + paletteItemOverlays = [], + paletteItemPositions = [], + paletteItemHoverOverlays = [], + iconOverlays = [], + staticOverlays = [], + + LEFT_HAND = 0, + + controlJointName, + + PALETTE_ORIGIN_POSITION = { + x: 0, + y: UIT.dimensions.handOffset, + z: UIT.dimensions.canvasSeparation + UIT.dimensions.canvas.x / 2 + }, + PALETTE_ORIGIN_ROTATION = Quat.ZERO, + paletteLateralOffset, + + PALETTE_ORIGIN_PROPERTIES = { + dimensions: { x: 0.005, y: 0.005, z: 0.005 }, + localPosition: PALETTE_ORIGIN_POSITION, + localRotation: PALETTE_ORIGIN_ROTATION, + color: { red: 255, blue: 0, green: 0 }, + alpha: 1.0, + parentID: Uuid.SELF, + ignoreRayIntersection: true, + visible: false + }, + + PALETTE_HEADER_HEADING_PROPERTIES = { + url: Script.resolvePath("../assets/gray-header.fbx"), + dimensions: UIT.dimensions.headerHeading, // Model is in rotated coordinate system but can override. + localPosition: { + x: 0, + y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.headerHeading.y / 2, + z: UIT.dimensions.headerHeading.z / 2 + }, + localRotation: Quat.ZERO, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + PALETTE_HEADER_BAR_PROPERTIES = { + url: Script.resolvePath("../assets/blue-header-bar.fbx"), + dimensions: UIT.dimensions.headerBar, // Model is in rotated coordinate system but can override. + localPosition: { + x: 0, + y: UIT.dimensions.canvas.y / 2 - UIT.dimensions.headerHeading.y - UIT.dimensions.headerBar.y / 2, + z: UIT.dimensions.headerBar.z / 2 + }, + localRotation: Quat.ZERO, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + PALETTE_TITLE_PROPERTIES = { + url: Script.resolvePath("../assets/create/create-heading.svg"), + scale: 0.0363, + localPosition: { + x: 0, + y: 0, + z: PALETTE_HEADER_HEADING_PROPERTIES.dimensions.z / 2 + UIT.dimensions.imageOverlayOffset + }, + localRotation: Quat.ZERO, + color: UIT.colors.white, + alpha: 1.0, + emissive: true, + ignoreRayIntersection: true, + isFacingAvatar: false, + visible: true + }, + + PALETTE_PANEL_PROPERTIES = { + dimensions: UIT.dimensions.panel, + localPosition: { x: 0, y: (UIT.dimensions.panel.y - UIT.dimensions.canvas.y) / 2, z: UIT.dimensions.panel.z / 2 }, + localRotation: Quat.ZERO, + color: UIT.colors.baseGray, + alpha: 1.0, + solid: true, + ignoreRayIntersection: false, + visible: true + }, + + ENTITY_CREATION_DIMENSIONS = { x: 0.2, y: 0.2, z: 0.2 }, + ENTITY_CREATION_COLOR = { red: 192, green: 192, blue: 192 }, + + PALETTE_ITEM = { + overlay: "cube", // Invisible cube for hit area. + properties: { + dimensions: UIT.dimensions.itemCollisionZone, + localRotation: Quat.ZERO, + alpha: 0.0, // Invisible. + solid: true, + ignoreRayIntersection: false, + visible: true // So that laser intersects. + }, + hoverButton: { + // Relative to root overlay. + overlay: "cube", + properties: { + dimensions: UIT.dimensions.paletteItemButtonDimensions, + localPosition: UIT.dimensions.paletteItemButtonOffset, + localRotation: Quat.ZERO, + color: UIT.colors.blueHighlight, + alpha: 1.0, + emissive: true, // TODO: This has no effect. + solid: true, + ignoreRayIntersection: true, + visible: false + } + }, + icon: { + // Relative to hoverButton. + overlay: "model", + properties: { + dimensions: UIT.dimensions.paletteItemIconDimensions, + localPosition: UIT.dimensions.paletteItemIconOffset, + localRotation: Quat.ZERO, + emissive: true, // TODO: This has no effect. + ignoreRayIntersection: true + } + }, + entity: { + dimensions: ENTITY_CREATION_DIMENSIONS + } + }, + + PALETTE_ITEMS = [ + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/cube.fbx") + } + }, + entity: { + type: "Box", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/sphere.fbx") + } + }, + entity: { + type: "Sphere", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/tetrahedron.fbx") + } + }, + entity: { + type: "Shape", + shape: "Tetrahedron", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/octahedron.fbx") + } + }, + entity: { + type: "Shape", + shape: "Octahedron", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/icosahedron.fbx") + } + }, + entity: { + type: "Shape", + shape: "Icosahedron", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/dodecahedron.fbx") + } + }, + entity: { + type: "Shape", + shape: "Dodecahedron", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/hexagon.fbx"), + dimensions: { x: 0.02078, y: 0.024, z: 0.024 }, + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + } + }, + entity: { + type: "Shape", + shape: "Hexagon", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/prism.fbx"), + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + } + }, + entity: { + type: "Shape", + shape: "Triangle", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/octagon.fbx"), + dimensions: { x: 0.023805, y: 0.024, z: 0.024 }, + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + } + }, + entity: { + type: "Shape", + shape: "Octagon", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/cylinder.fbx"), + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + } + }, + entity: { + type: "Shape", + shape: "Cylinder", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/cone.fbx"), + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + } + }, + entity: { + type: "Shape", + shape: "Cone", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + }, + { + icon: { + properties: { + url: Script.resolvePath("../assets/create/circle.fbx"), + dimensions: { x: 0.024, y: 0.0005, z: 0.024 }, + localRotation: Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + } + }, + entity: { + type: "Shape", + shape: "Circle", + dimensions: ENTITY_CREATION_DIMENSIONS, + color: ENTITY_CREATION_COLOR + } + } + ], + + isDisplaying = false, + + NONE = -1, + highlightedItem = NONE, + wasTriggerClicked = false, + otherSide, + + // References. + controlHand; + + if (!(this instanceof CreatePalette)) { + return new CreatePalette(); + } + + + function setHand(hand) { + // Assumes UI is not displaying. + var NUMBER_OF_HANDS = 2; + side = hand; + otherSide = (side + 1) % NUMBER_OF_HANDS; + controlHand = side === LEFT_HAND ? rightInputs.hand() : leftInputs.hand(); + controlJointName = side === LEFT_HAND ? "LeftHand" : "RightHand"; + paletteLateralOffset = side === LEFT_HAND ? -UIT.dimensions.handLateralOffset : UIT.dimensions.handLateralOffset; + } + + setHand(side); + + function getOverlayIDs() { + return [palettePanelOverlay, paletteHeaderHeadingOverlay, paletteHeaderBarOverlay].concat(paletteItemOverlays); + } + + function setVisible(visible) { + var i, + length; + + for (i = 0, length = staticOverlays.length; i < length; i++) { + Overlays.editOverlay(staticOverlays[i], { visible: visible }); + } + + if (!visible) { + for (i = 0, length = paletteItemHoverOverlays.length; i < length; i++) { + Overlays.editOverlay(paletteItemHoverOverlays[i], { visible: false }); + } + } + } + + function update(intersectionOverlayID) { + var itemIndex, + isTriggerClicked, + properties, + CREATE_OFFSET = { x: 0, y: 0.05, z: -0.02 }, + INVERSE_HAND_BASIS_ROTATION = Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), + entityID; + + itemIndex = paletteItemOverlays.indexOf(intersectionOverlayID); + + // Unhighlight and lower old item. + if (highlightedItem !== NONE && (itemIndex === NONE || itemIndex !== highlightedItem)) { + Overlays.editOverlay(paletteItemHoverOverlays[highlightedItem], { + localPosition: UIT.dimensions.paletteItemButtonOffset, + visible: false + }); + highlightedItem = NONE; + } + + // Highlight and raise new item. + if (itemIndex !== NONE && highlightedItem !== itemIndex) { + Feedback.play(otherSide, Feedback.HOVER_BUTTON); + Overlays.editOverlay(paletteItemHoverOverlays[itemIndex], { + localPosition: UIT.dimensions.paletteItemButtonHoveredOffset, + visible: true + }); + highlightedItem = itemIndex; + } + + // Press item and create new entity. + isTriggerClicked = controlHand.triggerClicked(); + if (highlightedItem !== NONE && isTriggerClicked && !wasTriggerClicked) { + // Create entity. + Feedback.play(otherSide, Feedback.CREATE_ENTITY); + properties = Object.clone(PALETTE_ITEMS[itemIndex].entity); + properties.position = Vec3.sum(controlHand.palmPosition(), + Vec3.multiplyQbyV(controlHand.orientation(), + Vec3.sum({ x: 0, y: properties.dimensions.z / 2, z: 0 }, CREATE_OFFSET))); + properties.rotation = Quat.multiply(controlHand.orientation(), INVERSE_HAND_BASIS_ROTATION); + entityID = Entities.addEntity(properties); + if (entityID !== Uuid.NULL) { + History.prePush( + otherSide, + { deleteEntities: [{ entityID: entityID }] }, + { createEntities: [{ entityID: entityID, properties: properties }] } + ); + } else { + Feedback.play(otherSide, Feedback.GENERAL_ERROR); + } + + // Lower and unhighlight item. + Overlays.editOverlay(paletteItemHoverOverlays[itemIndex], { + localPosition: UIT.dimensions.paletteItemButtonOffset, + visible: false + }); + + uiCommandCallback("autoGrab"); + } + + wasTriggerClicked = isTriggerClicked; + } + + function itemPosition(index) { + // Position relative to palette panel. + var ITEMS_PER_ROW = 4, + ROW_ZERO_Y_OFFSET = 0.0860, + ROW_SPACING = 0.0560, + COLUMN_ZERO_OFFSET = -0.08415, + COLUMN_SPACING = 0.0561, + row, + column; + + row = Math.floor(index / ITEMS_PER_ROW); + column = index % ITEMS_PER_ROW; + + return { + x: COLUMN_ZERO_OFFSET + column * COLUMN_SPACING, + y: ROW_ZERO_Y_OFFSET - row * ROW_SPACING, + z: UIT.dimensions.panel.z / 2 + UIT.dimensions.itemCollisionZone.z / 2 + }; + } + + function display() { + // Creates and shows menu entities. + var handJointIndex, + properties, + i, + length; + + if (isDisplaying) { + return; + } + + // Joint index. + handJointIndex = MyAvatar.getJointIndex(controlJointName); + if (handJointIndex === NONE) { + // Don't display if joint isn't available (yet) to attach to. + // User can clear this condition by toggling the app off and back on once avatar finishes loading. + App.log(side, "ERROR: CreatePalette: Hand joint index isn't available!"); + return; + } + + // Calculate position to put palette. + properties = Object.clone(PALETTE_ORIGIN_PROPERTIES); + properties.parentJointIndex = handJointIndex; + properties.localPosition = Vec3.sum(PALETTE_ORIGIN_POSITION, { x: paletteLateralOffset, y: 0, z: 0 }); + paletteOriginOverlay = Overlays.addOverlay("sphere", properties); + + // Header. + properties = Object.clone(PALETTE_HEADER_HEADING_PROPERTIES); + properties.parentID = paletteOriginOverlay; + paletteHeaderHeadingOverlay = Overlays.addOverlay("model", properties); + properties = Object.clone(PALETTE_HEADER_BAR_PROPERTIES); + properties.parentID = paletteOriginOverlay; + paletteHeaderBarOverlay = Overlays.addOverlay("model", properties); + properties = Object.clone(PALETTE_TITLE_PROPERTIES); + properties.parentID = paletteHeaderHeadingOverlay; + paletteTitleOverlay = Overlays.addOverlay("image3d", properties); + + // Palette background. + properties = Object.clone(PALETTE_PANEL_PROPERTIES); + properties.parentID = paletteOriginOverlay; + palettePanelOverlay = Overlays.addOverlay("cube", properties); + + // Palette items. + for (i = 0, length = PALETTE_ITEMS.length; i < length; i++) { + // Collision overlay. + properties = Object.clone(PALETTE_ITEM.properties); + properties.parentID = palettePanelOverlay; + properties.localPosition = itemPosition(i); + paletteItemOverlays[i] = Overlays.addOverlay(PALETTE_ITEM.overlay, properties); + paletteItemPositions[i] = properties.localPosition; + + // Highlight overlay. + properties = Object.clone(PALETTE_ITEM.hoverButton.properties); + properties.parentID = paletteItemOverlays[i]; + paletteItemHoverOverlays[i] = Overlays.addOverlay(PALETTE_ITEM.hoverButton.overlay, properties); + + // Icon overlay. + properties = Object.clone(PALETTE_ITEM.icon.properties); + properties = Object.merge(properties, PALETTE_ITEMS[i].icon.properties); + properties.parentID = paletteItemHoverOverlays[i]; + iconOverlays[i] = Overlays.addOverlay(PALETTE_ITEM.icon.overlay, properties); + } + + // Always-visible overlays. + staticOverlays = [].concat(paletteHeaderHeadingOverlay, paletteHeaderBarOverlay, paletteTitleOverlay, + palettePanelOverlay, paletteItemOverlays, iconOverlays); + + isDisplaying = true; + } + + function clear() { + // Deletes menu entities. + if (!isDisplaying) { + return; + } + Overlays.deleteOverlay(paletteOriginOverlay); // Automatically deletes all other overlays because they're children. + paletteItemOverlays = []; + paletteItemHoverOverlays = []; + iconOverlays = []; + staticOverlays = []; + isDisplaying = false; + } + + function destroy() { + clear(); + } + + return { + setHand: setHand, + overlayIDs: getOverlayIDs, + setVisible: setVisible, + update: update, + display: display, + clear: clear, + destroy: destroy + }; +}; + +CreatePalette.prototype = {}; diff --git a/unpublishedScripts/marketplace/shapes/modules/feedback.js b/unpublishedScripts/marketplace/shapes/modules/feedback.js new file mode 100644 index 0000000000..0d45ae2019 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/feedback.js @@ -0,0 +1,80 @@ +// +// feedback.js +// +// Created by David Rowe on 31 Aug 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 +// + +/* global Feedback:true */ + +Feedback = (function () { + // Provide audio and haptic user feedback. + // Global object. + + "use strict"; + + var DROP_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/drop.wav")), + DELETE_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/delete.wav")), + SELECT_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/select.wav")), + CLONE_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/clone.wav")), + CREATE_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/create.wav")), + EQUIP_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/equip.wav")), + ERROR_SOUND = SoundCache.getSound(Script.resolvePath("../assets/audio/error.wav")), + UNDO_SOUND = DROP_SOUND, + REDO_SOUND = DROP_SOUND, + + FEEDBACK_PARAMETERS = { + DROP_TOOL: { sound: DROP_SOUND, volume: 0.3, haptic: 0.75 }, + DELETE_ENTITY: { sound: DELETE_SOUND, volume: 0.5, haptic: 0.2 }, + SELECT_ENTITY: { sound: SELECT_SOUND, volume: 0.2, haptic: 0.1 }, // E.g., Group tool. + CLONE_ENTITY: { sound: CLONE_SOUND, volume: 0.2, haptic: 0.1 }, + CREATE_ENTITY: { sound: CREATE_SOUND, volume: 0.4, haptic: 0.2 }, + HOVER_MENU_ITEM: { sound: null, volume: 0, haptic: 0.1 }, // Tools menu. + HOVER_BUTTON: { sound: null, volume: 0, haptic: 0.075 }, // Tools options and Create palette items. + EQUIP_TOOL: { sound: EQUIP_SOUND, volume: 0.3, haptic: 0.6 }, + APPLY_PROPERTY: { sound: null, volume: 0, haptic: 0.3 }, + APPLY_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 }, + UNDO_ACTION: { sound: UNDO_SOUND, volume: 0.1, haptic: 0.2 }, + REDO_ACTION: { sound: REDO_SOUND, volume: 0.1, haptic: 0.2 }, + GENERAL_ERROR: { sound: ERROR_SOUND, volume: 0.2, haptic: 0.7 } + }, + + VOLUME_MULTPLIER = 0.5, // Resulting volume range should be within 0.0 - 1.0. + HAPTIC_STRENGTH_MULTIPLIER = 1.3, // Resulting strength range should be within 0.0 - 1.0. + HAPTIC_LENGTH_MULTIPLIER = 75.0; // Resulting length range should be within 0 - 50, say. + + function play(side, item) { + var parameters = FEEDBACK_PARAMETERS[item]; + + if (parameters.sound) { + Audio.playSound(parameters.sound, { + position: side ? MyAvatar.getRightPalmPosition() : MyAvatar.getLeftPalmPosition(), + volume: parameters.volume * VOLUME_MULTPLIER, + localOnly: true + }); + } + + Controller.triggerHapticPulse(parameters.haptic * HAPTIC_STRENGTH_MULTIPLIER, + parameters.haptic * HAPTIC_LENGTH_MULTIPLIER, side); + } + + return { + DROP_TOOL: "DROP_TOOL", + DELETE_ENTITY: "DELETE_ENTITY", + SELECT_ENTITY: "SELECT_ENTITY", + CLONE_ENTITY: "CLONE_ENTITY", + CREATE_ENTITY: "CREATE_ENTITY", + HOVER_MENU_ITEM: "HOVER_MENU_ITEM", + HOVER_BUTTON: "HOVER_BUTTON", + EQUIP_TOOL: "EQUIP_TOOL", + APPLY_PROPERTY: "APPLY_PROPERTY", + APPLY_ERROR: "APPLY_ERROR", + UNDO_ACTION: "UNDO_ACTION", + REDO_ACTION: "REDO_ACTION", + GENERAL_ERROR: "GENERAL_ERROR", + play: play + }; +}()); diff --git a/unpublishedScripts/marketplace/shapes/modules/groups.js b/unpublishedScripts/marketplace/shapes/modules/groups.js new file mode 100644 index 0000000000..3153a622ee --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/groups.js @@ -0,0 +1,275 @@ +// +// groups.js +// +// Created by David Rowe on 1 Aug 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 +// + +/* global Groups:true, App, History */ + +Groups = function () { + // Groups and ungroups trees of entities. + + "use strict"; + + var rootEntityIDs = [], + selections = [], + entitiesSelectedCount = 0; + + if (!(this instanceof Groups)) { + return new Groups(); + } + + function add(selection) { + rootEntityIDs.push(selection[0].id); + selections.push(Object.clone(selection)); + entitiesSelectedCount += selection.length; + } + + function remove(selection) { + var index = rootEntityIDs.indexOf(selection[0].id); + + entitiesSelectedCount -= selections[index].length; + rootEntityIDs.splice(index, 1); + selections.splice(index, 1); + } + + function toggle(selection) { + if (selection.length === 0) { + return; + } + + if (rootEntityIDs.indexOf(selection[0].id) === -1) { + add(selection); + } else { + remove(selection); + } + } + + function selection(excludes) { + var result = [], + i, + lengthI, + j, + lengthJ; + + for (i = 0, lengthI = rootEntityIDs.length; i < lengthI; i++) { + if (excludes.indexOf(rootEntityIDs[i]) === -1) { + for (j = 0, lengthJ = selections[i].length; j < lengthJ; j++) { + result.push(selections[i][j]); + } + } + } + + return result; + } + + function includes(rootEntityID) { + return rootEntityIDs.indexOf(rootEntityID) !== -1; + } + + function getRootEntityIDs() { + return rootEntityIDs; + } + + function groupsCount() { + return selections.length; + } + + function entitiesCount() { + return entitiesSelectedCount; + } + + function group() { + // Groups all selections into one. + var DYNAMIC_AND_COLLISIONLESS = { dynamic: true, collisionless: true }, + rootID, + undoData = [], + redoData = [], + i, + lengthI, + j, + lengthJ; + + // If the first group has physics (i.e., root entity is dynamic) make all entities in child groups dynamic and + // collisionless. (Don't need to worry about other groups physics properties because only those of the first entity in + // the linkset are used by High Fidelity.) See Selection.applyPhysics(). + if (selections[0][0].dynamic) { + for (i = 1, lengthI = selections.length; i < lengthI; i++) { + for (j = 0, lengthJ = selections[i].length; j < lengthJ; j++) { + undoData.push({ + entityID: selections[i][j].id, + properties: { + dynamic: selections[i][j].dynamic, + collisionless: selections[i][j].collisionless + } + }); + Entities.editEntity(selections[i][j].id, DYNAMIC_AND_COLLISIONLESS); + selections[i][j].dynamic = true; + selections[i][j].collisionless = true; + redoData.push({ + entityID: selections[i][j].id, + properties: DYNAMIC_AND_COLLISIONLESS + }); + } + } + } + + // Make the first entity in the first group the root and link the first entities of all other groups to it. + rootID = rootEntityIDs[0]; + for (i = 1, lengthI = rootEntityIDs.length; i < lengthI; i++) { + undoData.push({ + entityID: rootEntityIDs[i], + properties: { parentID: Uuid.NULL } + }); + Entities.editEntity(rootEntityIDs[i], { + parentID: rootID + }); + redoData.push({ + entityID: rootEntityIDs[i], + properties: { parentID: rootID } + }); + } + + // Update selection. + rootEntityIDs.splice(1, rootEntityIDs.length - 1); + for (i = 1, lengthI = selections.length; i < lengthI; i++) { + selections[i][0].parentID = rootID; + selections[0] = selections[0].concat(selections[i]); + } + selections.splice(1, selections.length - 1); + + // Add history entry. + History.push(null, { setProperties: undoData }, { setProperties: redoData }); + } + + function ungroup() { + // Ungroups the first and assumed to be only selection. + // If the first entity in the selection has a mix of solo and group children then just the group children are unlinked, + // otherwise all are unlinked. + var rootID, + childrenIDs = [], + childrenIndexes = [], + childrenIndexIsGroup = [], + previousWasGroup, + hasSoloChildren = false, + hasGroupChildren = false, + isUngroupAll, + NONDYNAMIC_AND_NONCOLLISIONLESS = { dynamic: false, collisionless: false }, + undoData = [], + redoData = [], + i, + lengthI, + j, + lengthJ; + + function updateGroupInformation() { + var childrenIndexesLength = childrenIndexes.length; + if (childrenIndexesLength > 1) { + previousWasGroup = childrenIndexes[childrenIndexesLength - 2] < i - 1; + childrenIndexIsGroup.push(previousWasGroup); + if (previousWasGroup) { + hasGroupChildren = true; + } else { + hasSoloChildren = true; + } + } + } + + if (entitiesSelectedCount === 0) { + App.log("ERROR: Groups: Nothing to ungroup!"); + return; + } + if (entitiesSelectedCount === 1) { + App.log("ERROR: Groups: Cannot ungroup sole entity!"); + return; + } + + // Compile information on immediate children. + rootID = rootEntityIDs[0]; + for (i = 1, lengthI = selections[0].length; i < lengthI; i++) { + if (selections[0][i].parentID === rootID) { + childrenIDs.push(selections[0][i].id); + childrenIndexes.push(i); + updateGroupInformation(); + } + } + childrenIndexes.push(selections[0].length); // Special extra item at end to aid updating selection. + updateGroupInformation(); + + // Unlink children. + isUngroupAll = hasSoloChildren !== hasGroupChildren; + for (i = childrenIDs.length - 1; i >= 0; i--) { + if (isUngroupAll || childrenIndexIsGroup[i]) { + undoData.push({ + entityID: childrenIDs[i], + properties: { parentID: selections[0][childrenIndexes[i]].parentID } + }); + Entities.editEntity(childrenIDs[i], { + parentID: Uuid.NULL + }); + redoData.push({ + entityID: childrenIDs[i], + properties: { parentID: Uuid.NULL } + }); + rootEntityIDs.push(childrenIDs[i]); + selections[0][childrenIndexes[i]].parentID = Uuid.NULL; + selections.push(selections[0].splice(childrenIndexes[i], childrenIndexes[i + 1] - childrenIndexes[i])); + } + } + + // If root group has physics, reset child groups to defaults for dynamic and collisionless. See + // Selection.applyPhysics(). + if (selections[0][0].dynamic) { + for (i = 1, lengthI = selections.length; i < lengthI; i++) { + for (j = 0, lengthJ = selections[i].length; j < lengthJ; j++) { + undoData.push({ + entityID: selections[i][j].id, + properties: { + dynamic: selections[i][j].dynamic, + collisionless: selections[i][j].collisionless + } + }); + Entities.editEntity(selections[i][j].id, NONDYNAMIC_AND_NONCOLLISIONLESS); + selections[i][j].dynamic = false; + selections[i][j].collisionless = false; + redoData.push({ + entityID: selections[i][j].id, + properties: NONDYNAMIC_AND_NONCOLLISIONLESS + }); + } + } + } + + // Add history entry. + History.push(null, { setProperties: undoData }, { setProperties: redoData }); + } + + function clear() { + rootEntityIDs = []; + selections = []; + entitiesSelectedCount = 0; + } + + function destroy() { + clear(); + } + + return { + toggle: toggle, + selection: selection, + includes: includes, + rootEntityIDs: getRootEntityIDs, + groupsCount: groupsCount, + entitiesCount: entitiesCount, + group: group, + ungroup: ungroup, + clear: clear, + destroy: destroy + }; +}; + +Groups.prototype = {}; diff --git a/unpublishedScripts/marketplace/shapes/modules/hand.js b/unpublishedScripts/marketplace/shapes/modules/hand.js new file mode 100644 index 0000000000..73efed8017 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/hand.js @@ -0,0 +1,250 @@ +// +// hand.js +// +// Created by David Rowe on 21 Jul 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 +// + +/* global Hand:true */ + +Hand = function (side) { + + "use strict"; + + // Hand controller input. + var handController, + controllerTrigger, + controllerTriggerClicked, + controllerGrip, + + isGripClicked = false, + isGripClickedHandled = false, + GRIP_ON_VALUE = 0.99, + GRIP_OFF_VALUE = 0.95, + + isTriggerPressed, + isTriggerClicked, + TRIGGER_ON_VALUE = 0.15, // Per controllerDispatcherUtils.js. + TRIGGER_OFF_VALUE = 0.1, // Per controllerDispatcherUtils.js. + TRIGGER_CLICKED_VALUE = 1.0, + + NEAR_GRAB_RADIUS = 0.05, // Different from controllerDispatcherUtils.js. + NEAR_HOVER_RADIUS = 0.025, + + LEFT_HAND = 0, + HALF_TREE_SCALE = 16384, + + handPose, + handPosition, + handOrientation, + palmPosition, + + handleOverlayIDs = [], + intersection = {}; + + if (!(this instanceof Hand)) { + return new Hand(side); + } + + if (side === LEFT_HAND) { + handController = Controller.Standard.LeftHand; + controllerTrigger = Controller.Standard.LT; + controllerTriggerClicked = Controller.Standard.LTClick; + controllerGrip = Controller.Standard.LeftGrip; + } else { + handController = Controller.Standard.RightHand; + controllerTrigger = Controller.Standard.RT; + controllerTriggerClicked = Controller.Standard.RTClick; + controllerGrip = Controller.Standard.RightGrip; + } + + function setHandleOverlays(overlayIDs) { + handleOverlayIDs = overlayIDs; + } + + function valid() { + return handPose.valid; + } + + function position() { + return handPosition; + } + + function orientation() { + return handOrientation; + } + + function getPalmPosition() { + return palmPosition; + } + + function triggerPressed() { + return isTriggerPressed; + } + + function triggerClicked() { + return isTriggerClicked; + } + + function gripClicked() { + return isGripClicked; + } + + function setGripClickedHandled() { + isGripClicked = false; + isGripClickedHandled = true; + } + + function getIntersection() { + return intersection; + } + + function getNearGrabRadius() { + return NEAR_GRAB_RADIUS; + } + + function update() { + var gripValue, + overlayID, + overlayIDs, + overlayDistance, + intersectionPosition, + distance, + entityID, + entityIDs, + entitySize, + size, + i, + length; + + + // Hand pose. + handPose = Controller.getPoseValue(handController); + if (!handPose.valid) { + intersection = {}; + return; + } + handPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, handPose.translation), MyAvatar.position); + handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); + + // Controller trigger. + isTriggerPressed = Controller.getValue(controllerTrigger) > (isTriggerPressed + ? TRIGGER_OFF_VALUE : TRIGGER_ON_VALUE); + isTriggerClicked = Controller.getValue(controllerTriggerClicked) === TRIGGER_CLICKED_VALUE; + + // Controller grip. + gripValue = Controller.getValue(controllerGrip); + if (isGripClicked) { + isGripClicked = gripValue > GRIP_OFF_VALUE; + } else { + isGripClicked = gripValue > GRIP_ON_VALUE; + } + // Grip clicked may be being handled by UI. + if (isGripClicked) { + isGripClicked = !isGripClickedHandled; + } else { + isGripClickedHandled = false; + } + + // Hand-overlay intersection, if any handle overlays. + overlayID = null; + palmPosition = side === LEFT_HAND ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + if (handleOverlayIDs.length > 0) { + overlayIDs = Overlays.findOverlays(palmPosition, NEAR_HOVER_RADIUS); + if (overlayIDs.length > 0) { + // Typically, there will be only one overlay; optimize for that case. + overlayID = overlayIDs[0]; + if (overlayIDs.length > 1) { + // Find closest overlay. + overlayDistance = Vec3.length(Vec3.subtract(Overlays.getProperty(overlayID, "position"), palmPosition)); + for (i = 1, length = overlayIDs.length; i < length; i++) { + distance = + Vec3.length(Vec3.subtract(Overlays.getProperty(overlayIDs[i], "position"), palmPosition)); + if (distance > overlayDistance) { + overlayID = overlayIDs[i]; + overlayDistance = distance; + } + } + } + } + if (overlayID && handleOverlayIDs.indexOf(overlayID) === -1) { + overlayID = null; + } + if (overlayID) { + intersectionPosition = Overlays.getProperty(overlayID, "position"); + } + } + + // Hand-entity intersection, if any editable, if overlay not intersected. + entityID = null; + if (overlayID === null) { + // palmPosition is set above. + entityIDs = Entities.findEntities(palmPosition, NEAR_GRAB_RADIUS); + if (entityIDs.length > 0) { + // Typically, there will be only one entity; optimize for that case. + if (Entities.hasEditableRoot(entityIDs[0])) { + entityID = entityIDs[0]; + } + if (entityIDs.length > 1) { + // Find smallest, editable entity. + entitySize = HALF_TREE_SCALE; + for (i = 0, length = entityIDs.length; i < length; i++) { + if (Entities.hasEditableRoot(entityIDs[i])) { + size = Vec3.length(Entities.getEntityProperties(entityIDs[i], "dimensions").dimensions); + if (size < entitySize) { + entityID = entityIDs[i]; + entitySize = size; + } + } + } + } + } + if (entityID) { + intersectionPosition = Entities.getEntityProperties(entityID, "position").position; + if (Vec3.distance(palmPosition, intersectionPosition) > NEAR_GRAB_RADIUS) { + intersectionPosition = Vec3.sum(palmPosition, + Vec3.multiply(NEAR_GRAB_RADIUS, Vec3.normalize(Vec3.subtract(intersectionPosition, palmPosition)))); + } + } + } + + intersection = { + intersects: overlayID !== null || entityID !== null, + overlayID: overlayID, + entityID: entityID, + handIntersected: overlayID !== null || entityID !== null, + editableEntity: entityID !== null, + intersection: intersectionPosition + }; + } + + function clear() { + // Nothing to do. + } + + function destroy() { + // Nothing to do. + } + + return { + setHandleOverlays: setHandleOverlays, + valid: valid, + position: position, + orientation: orientation, + palmPosition: getPalmPosition, + triggerPressed: triggerPressed, + triggerClicked: triggerClicked, + gripClicked: gripClicked, + setGripClickedHandled: setGripClickedHandled, + intersection: getIntersection, + getNearGrabRadius: getNearGrabRadius, + update: update, + clear: clear, + destroy: destroy + }; +}; + +Hand.prototype = {}; diff --git a/unpublishedScripts/marketplace/shapes/modules/handles.js b/unpublishedScripts/marketplace/shapes/modules/handles.js new file mode 100644 index 0000000000..d7627e8626 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/handles.js @@ -0,0 +1,372 @@ +// +// handles.js +// +// Created by David Rowe on 21 Jul 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 +// + +/* global Handles:true */ + +Handles = function (side) { + // Draws scaling handles. + + "use strict"; + + var boundingBoxOverlay, + boundingBoxDimensions, + boundingBoxLocalCenter, + cornerIndexes = [], + cornerHandleOverlays = [], + faceHandleOverlays = [], + faceHandleOffsets, + BOUNDING_BOX_COLOR = { red: 0, green: 240, blue: 240 }, + BOUNDING_BOX_ALPHA = 0.8, + HANDLE_NORMAL_COLOR = { red: 0, green: 240, blue: 240 }, + HANDLE_HOVER_COLOR = { red: 0, green: 255, blue: 120 }, + HANDLE_NORMAL_ALPHA = 0.7, + HANDLE_HOVER_ALPHA = 0.9, + NUM_CORNERS = 8, + NUM_CORNER_HANDLES = 2, + CORNER_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.1, z: 0.1 }, + CORNER_HANDLE_OVERLAY_AXES, + NUM_FACE_HANDLES = 6, + FACE_HANDLE_OVERLAY_DIMENSIONS = { x: 0.1, y: 0.12, z: 0.1 }, + FACE_HANDLE_OVERLAY_AXES, + FACE_HANDLE_OVERLAY_OFFSETS, + FACE_HANDLE_OVERLAY_ROTATIONS, + FACE_HANDLE_OVERLAY_SCALE_AXES, + DISTANCE_MULTIPLIER_MULTIPLIER = 0.25, + hoveredOverlayID = null, + isVisible = false, + + // Scaling. + scalingBoundingBoxDimensions, + scalingBoundingBoxLocalCenter, + + i; + + if (!(this instanceof Handles)) { + return new Handles(side); + } + + CORNER_HANDLE_OVERLAY_AXES = [ + // Ordered such that items 4 apart are opposite corners - used in display(). + { x: -0.5, y: -0.5, z: -0.5 }, + { x: -0.5, y: -0.5, z: 0.5 }, + { x: -0.5, y: 0.5, z: -0.5 }, + { x: -0.5, y: 0.5, z: 0.5 }, + { x: 0.5, y: 0.5, z: 0.5 }, + { x: 0.5, y: 0.5, z: -0.5 }, + { x: 0.5, y: -0.5, z: 0.5 }, + { x: 0.5, y: -0.5, z: -0.5 } + ]; + + FACE_HANDLE_OVERLAY_AXES = [ + { x: -0.5, y: 0, z: 0 }, + { x: 0.5, y: 0, z: 0 }, + { x: 0, y: -0.5, z: 0 }, + { x: 0, y: 0.5, z: 0 }, + { x: 0, y: 0, z: -0.5 }, + { x: 0, y: 0, z: 0.5 } + ]; + + FACE_HANDLE_OVERLAY_OFFSETS = { + x: FACE_HANDLE_OVERLAY_DIMENSIONS.y, + y: FACE_HANDLE_OVERLAY_DIMENSIONS.y, + z: FACE_HANDLE_OVERLAY_DIMENSIONS.y + }; + + FACE_HANDLE_OVERLAY_ROTATIONS = [ + Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }), + Quat.fromVec3Degrees({ x: 0, y: 0, z: -90 }), + Quat.fromVec3Degrees({ x: 180, y: 0, z: 0 }), + Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }), + Quat.fromVec3Degrees({ x: -90, y: 0, z: 0 }), + Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }) + ]; + + FACE_HANDLE_OVERLAY_SCALE_AXES = [ + Vec3.UNIT_NEG_X, + Vec3.UNIT_X, + Vec3.UNIT_NEG_Y, + Vec3.UNIT_Y, + Vec3.UNIT_NEG_Z, + Vec3.UNIT_Z + ]; + + function isAxisHandle(overlayID) { + return faceHandleOverlays.indexOf(overlayID) !== -1; + } + + function isCornerHandle(overlayID) { + return cornerHandleOverlays.indexOf(overlayID) !== -1; + } + + function isHandle(overlayID) { + return isAxisHandle(overlayID) || isCornerHandle(overlayID); + } + + function handleOffset(overlayID) { + // Distance from overlay position to entity surface. + if (isCornerHandle(overlayID)) { + return 0; // Corner overlays are centered on the corner. + } + return faceHandleOffsets.y / 2; + } + + function getOverlays() { + return [].concat(cornerHandleOverlays, faceHandleOverlays); + } + + function scalingAxis(overlayID) { + var axesIndex; + if (isCornerHandle(overlayID)) { + axesIndex = CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[cornerHandleOverlays.indexOf(overlayID)]]; + return Vec3.normalize(Vec3.multiplyVbyV(axesIndex, boundingBoxDimensions)); + } + return FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]; + } + + function scalingDirections(overlayID) { + if (isCornerHandle(overlayID)) { + return Vec3.ONE; + } + return Vec3.abs(FACE_HANDLE_OVERLAY_SCALE_AXES[faceHandleOverlays.indexOf(overlayID)]); + } + + function display(rootEntityID, boundingBox, isMultipleEntities, isSuppressZAxis) { + var boundingBoxCenter, + boundingBoxOrientation, + cameraPosition, + boundingBoxVector, + distanceMultiplier, + cameraUp, + cornerPosition, + cornerVector, + crossProductScale, + maxCrossProductScale, + rightCornerIndex, + leftCornerIndex, + cornerHandleDimensions, + faceHandleDimensions, + i; + + isVisible = true; + + boundingBoxDimensions = boundingBox.dimensions; + boundingBoxCenter = boundingBox.center; + boundingBoxLocalCenter = boundingBox.localCenter; + boundingBoxOrientation = boundingBox.orientation; + + // Selection bounding box. + boundingBoxOverlay = Overlays.addOverlay("cube", { + parentID: rootEntityID, + localPosition: boundingBoxLocalCenter, + localRotation: Quat.ZERO, + dimensions: boundingBoxDimensions, + color: BOUNDING_BOX_COLOR, + alpha: BOUNDING_BOX_ALPHA, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: true + }); + + // Somewhat maintain general angular size of scale handles per bounding box center but make more distance ones + // display smaller in order to give comfortable depth cue. + cameraPosition = Camera.position; + boundingBoxVector = Vec3.subtract(boundingBox.center, Camera.position); + distanceMultiplier = Vec3.length(boundingBoxVector); + distanceMultiplier = DISTANCE_MULTIPLIER_MULTIPLIER + * (distanceMultiplier + (1 - Math.LOG10E * Math.log(distanceMultiplier + 1))); + + // Corner scale handles. + // At right-most and opposite corners of bounding box. + cameraUp = Quat.getUp(Camera.orientation); + maxCrossProductScale = 0; + for (i = 0; i < NUM_CORNERS; i++) { + cornerPosition = Vec3.sum(boundingBoxCenter, + Vec3.multiplyQbyV(boundingBoxOrientation, + Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[i], boundingBoxDimensions))); + cornerVector = Vec3.subtract(cornerPosition, cameraPosition); + crossProductScale = Vec3.dot(Vec3.cross(cornerVector, boundingBoxVector), cameraUp); + if (crossProductScale > maxCrossProductScale) { + maxCrossProductScale = crossProductScale; + rightCornerIndex = i; + } + } + leftCornerIndex = (rightCornerIndex + 4) % NUM_CORNERS; + cornerIndexes[0] = leftCornerIndex; + cornerIndexes[1] = rightCornerIndex; + cornerHandleDimensions = Vec3.multiply(distanceMultiplier, CORNER_HANDLE_OVERLAY_DIMENSIONS); + for (i = 0; i < NUM_CORNER_HANDLES; i++) { + cornerHandleOverlays[i] = Overlays.addOverlay("sphere", { + parentID: rootEntityID, + localPosition: Vec3.sum(boundingBoxLocalCenter, + Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], boundingBoxDimensions)), + localRotation: Quat.ZERO, + dimensions: cornerHandleDimensions, + color: HANDLE_NORMAL_COLOR, + alpha: HANDLE_NORMAL_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: false, + visible: true + }); + } + + // Face scale handles. + // Only valid for a single entity because for multiple entities, some may be at an angle relative to the root entity + // which would necessitate a (non-existent) shear transform be applied to them when scaling a face of the set. + if (!isMultipleEntities) { + faceHandleDimensions = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_DIMENSIONS); + faceHandleOffsets = Vec3.multiply(distanceMultiplier, FACE_HANDLE_OVERLAY_OFFSETS); + for (i = 0; i < NUM_FACE_HANDLES; i++) { + if (!isSuppressZAxis || FACE_HANDLE_OVERLAY_AXES[i].z === 0) { + faceHandleOverlays[i] = Overlays.addOverlay("shape", { + parentID: rootEntityID, + localPosition: Vec3.sum(boundingBoxLocalCenter, + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], Vec3.sum(boundingBoxDimensions, faceHandleOffsets))), + localRotation: FACE_HANDLE_OVERLAY_ROTATIONS[i], + dimensions: faceHandleDimensions, + shape: "Cone", + color: HANDLE_NORMAL_COLOR, + alpha: HANDLE_NORMAL_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: false, + visible: true + }); + } + } + } else { + faceHandleOverlays = []; + } + } + + function startScaling() { + // Nothing to do. + } + + function scale(scale3D) { + // Scale relative to dimensions and positions at start of scaling. + + // Selection bounding box. + scalingBoundingBoxDimensions = Vec3.multiplyVbyV(scale3D, boundingBoxLocalCenter); + scalingBoundingBoxLocalCenter = Vec3.multiplyVbyV(scale3D, boundingBoxDimensions); + Overlays.editOverlay(boundingBoxOverlay, { + localPosition: scalingBoundingBoxDimensions, + dimensions: scalingBoundingBoxLocalCenter + }); + + // Corner scale handles. + for (i = 0; i < NUM_CORNER_HANDLES; i++) { + Overlays.editOverlay(cornerHandleOverlays[i], { + localPosition: Vec3.sum(scalingBoundingBoxDimensions, + Vec3.multiplyVbyV(CORNER_HANDLE_OVERLAY_AXES[cornerIndexes[i]], scalingBoundingBoxLocalCenter)) + }); + } + + // Face scale handles. + if (faceHandleOverlays.length > 0) { + for (i = 0; i < NUM_FACE_HANDLES; i++) { + Overlays.editOverlay(faceHandleOverlays[i], { + localPosition: Vec3.sum(scalingBoundingBoxDimensions, + Vec3.multiplyVbyV(FACE_HANDLE_OVERLAY_AXES[i], + Vec3.sum(scalingBoundingBoxLocalCenter, faceHandleOffsets))) + }); + } + } + } + + function finishScaling() { + // Adopt final scale. + boundingBoxLocalCenter = scalingBoundingBoxDimensions; + boundingBoxDimensions = scalingBoundingBoxLocalCenter; + } + + function hover(overlayID) { + if (overlayID !== hoveredOverlayID) { + if (hoveredOverlayID !== null) { + Overlays.editOverlay(hoveredOverlayID, { color: HANDLE_NORMAL_COLOR }); + hoveredOverlayID = null; + } + + if (overlayID !== null + && (faceHandleOverlays.indexOf(overlayID) !== -1 || cornerHandleOverlays.indexOf(overlayID) !== -1)) { + Overlays.editOverlay(overlayID, { + color: HANDLE_HOVER_COLOR, + alpha: HANDLE_HOVER_ALPHA + }); + hoveredOverlayID = overlayID; + } + } + } + + function grab(overlayID) { + var overlay, + isShowAll = overlayID === null, + color = isShowAll ? HANDLE_NORMAL_COLOR : HANDLE_HOVER_COLOR, + alpha = isShowAll ? HANDLE_NORMAL_ALPHA : HANDLE_HOVER_ALPHA, + i, + length; + + for (i = 0, length = cornerHandleOverlays.length; i < length; i++) { + overlay = cornerHandleOverlays[i]; + Overlays.editOverlay(overlay, { + visible: isVisible && (isShowAll || overlay === overlayID), + color: color, + alpha: alpha + }); + } + + for (i = 0, length = faceHandleOverlays.length; i < length; i++) { + overlay = faceHandleOverlays[i]; + Overlays.editOverlay(overlay, { + visible: isVisible && (isShowAll || overlay === overlayID), + color: color, + alpha: alpha + }); + } + } + + function clear() { + var i, + length; + + Overlays.deleteOverlay(boundingBoxOverlay); + for (i = 0; i < NUM_CORNER_HANDLES; i++) { + Overlays.deleteOverlay(cornerHandleOverlays[i]); + } + for (i = 0, length = faceHandleOverlays.length; i < length; i++) { + Overlays.deleteOverlay(faceHandleOverlays[i]); + } + + isVisible = false; + } + + function destroy() { + clear(); + } + + return { + display: display, + overlays: getOverlays, + isHandle: isHandle, + handleOffset: handleOffset, + scalingAxis: scalingAxis, + scalingDirections: scalingDirections, + startScaling: startScaling, + scale: scale, + finishScaling: finishScaling, + hover: hover, + grab: grab, + clear: clear, + destroy: destroy + }; +}; + +Handles.prototype = {}; diff --git a/unpublishedScripts/marketplace/shapes/modules/highlights.js b/unpublishedScripts/marketplace/shapes/modules/highlights.js new file mode 100644 index 0000000000..98c200a808 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/highlights.js @@ -0,0 +1,167 @@ +// +// highlights.js +// +// Created by David Rowe on 21 Jul 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 +// + +/* global Highlights: true */ + +Highlights = function (side) { + // Draws highlights on selected entities. + + "use strict"; + + var handOverlay, + entityOverlays = [], + boundingBoxOverlay, + isDisplayingBoundingBox = false, + HIGHLIGHT_COLOR = { red: 240, green: 240, blue: 0 }, + SCALE_COLOR = { red: 0, green: 240, blue: 240 }, + GROUP_COLOR = { red: 220, green: 60, blue: 220 }, + HAND_HIGHLIGHT_ALPHA = 0.35, + ENTITY_HIGHLIGHT_ALPHA = 0.8, + BOUNDING_BOX_ALPHA = 0.8, + HAND_HIGHLIGHT_OFFSET = { x: 0.0, y: 0.11, z: 0.02 }, + LEFT_HAND = 0; + + if (!(this instanceof Highlights)) { + return new Highlights(); + } + + handOverlay = Overlays.addOverlay("sphere", { + dimension: Vec3.ZERO, + parentID: Uuid.SELF, + parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND + ? "_CONTROLLER_LEFTHAND" + : "_CONTROLLER_RIGHTHAND"), + localPosition: HAND_HIGHLIGHT_OFFSET, + alpha: HAND_HIGHLIGHT_ALPHA, + solid: true, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }); + + boundingBoxOverlay = Overlays.addOverlay("cube", { + alpha: BOUNDING_BOX_ALPHA, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + }); + + function setHandHighlightRadius(radius) { + var dimension = 2 * radius; + Overlays.editOverlay(handOverlay, { + dimensions: { x: dimension, y: dimension, z: dimension } + }); + } + + function maybeAddEntityOverlay(index) { + if (index >= entityOverlays.length) { + entityOverlays.push(Overlays.addOverlay("cube", { + alpha: ENTITY_HIGHLIGHT_ALPHA, + solid: false, + drawInFront: true, + ignoreRayIntersection: true, + visible: false + })); + } + } + + function editEntityOverlay(index, details, overlayColor) { + var offset = Vec3.multiplyVbyV(Vec3.subtract(Vec3.HALF, details.registrationPoint), details.dimensions); + + Overlays.editOverlay(entityOverlays[index], { + parentID: details.id, + localPosition: offset, + localRotation: Quat.ZERO, + dimensions: details.dimensions, + color: overlayColor, + visible: true + }); + } + + function display(handIntersected, selection, entityIndex, boundingBox, overlayColor) { + // Displays highlight for just entityIndex if non-null, otherwise highlights whole selection. + var i, + length; + + // Show/hide hand overlay. + Overlays.editOverlay(handOverlay, { + color: overlayColor, + visible: handIntersected + }); + + // Display entity overlays. + if (entityIndex !== null) { + // Add/edit entity overlay for just entityIndex. + maybeAddEntityOverlay(0); + editEntityOverlay(0, selection[entityIndex], overlayColor); + } else { + // Add/edit entity overlays for all entities in selection. + for (i = 0, length = selection.length; i < length; i++) { + maybeAddEntityOverlay(i); + editEntityOverlay(i, selection[i], overlayColor); + } + } + + // Delete extra entity overlays. + for (i = entityOverlays.length - 1, length = selection.length; i >= length; i--) { + Overlays.deleteOverlay(entityOverlays[i]); + entityOverlays.splice(i, 1); + } + + // Update bounding box overlay. + if (boundingBox !== null) { + Overlays.editOverlay(boundingBoxOverlay, { + position: boundingBox.center, + rotation: boundingBox.orientation, + dimensions: boundingBox.dimensions, + color: overlayColor, + visible: true + }); + isDisplayingBoundingBox = true; + } else if (isDisplayingBoundingBox) { + Overlays.editOverlay(boundingBoxOverlay, { visible: false }); + isDisplayingBoundingBox = false; + } + } + + function clear() { + var i, + length; + + // Hide hand and bounding box overlays. + Overlays.editOverlay(handOverlay, { visible: false }); + Overlays.editOverlay(boundingBoxOverlay, { visible: false }); + + // Delete entity overlays. + for (i = 0, length = entityOverlays.length; i < length; i++) { + Overlays.deleteOverlay(entityOverlays[i]); + } + entityOverlays = []; + } + + function destroy() { + clear(); + Overlays.deleteOverlay(handOverlay); + Overlays.deleteOverlay(boundingBoxOverlay); + } + + return { + HIGHLIGHT_COLOR: HIGHLIGHT_COLOR, + SCALE_COLOR: SCALE_COLOR, + GROUP_COLOR: GROUP_COLOR, + setHandHighlightRadius: setHandHighlightRadius, + display: display, + clear: clear, + destroy: destroy + }; +}; + +Highlights.prototype = {}; diff --git a/unpublishedScripts/marketplace/shapes/modules/history.js b/unpublishedScripts/marketplace/shapes/modules/history.js new file mode 100644 index 0000000000..9f2c45046a --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/history.js @@ -0,0 +1,246 @@ +// +// history.js +// +// Created by David Rowe on 12 Sep 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 +// + +/* global History: true */ + +History = (function () { + // Provides undo facility. + // Global object. + + "use strict"; + + var history = [ + /* + { + undoData: { + setProperties: [ + { + entityID: , + properties: { : , : , ... } + } + ], + createEntities: [ + { + entityID: , + properties: { : , : , ... } + } + ], + deleteEntities: [ + { + entityID: , + properties: { : , : , ... } + } + ] + }, + redoData: { + "" + } + } + */ + ], + MAX_HISTORY_ITEMS = 1000, + undoPosition = -1, // The next history item to undo; the next history item to redo = undoIndex + 1. + undoData = [{}, {}, {}], // Left side, right side, no side. + redoData = [{}, {}, {}], + NO_SIDE = 2; + + function doKick(entityID) { + var properties, + NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine", "ParticleEffect"], // Don't respond to gravity so don't kick. + DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; + + properties = Entities.getEntityProperties(entityID, ["type", "dynamic", "gravity", "velocity"]); + if (NO_KICK_ENTITY_TYPES.indexOf(properties.type) === -1 && properties.dynamic + && Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { + Entities.editEntity(entityID, { velocity: DYNAMIC_VELOCITY_KICK }); + } + } + + function kickPhysics(entityID) { + // Gives entities a small kick to start off physics, if necessary. + var KICK_DELAY = 750; // ms + + // Give physics a chance to catch up. Avoids some erratic behavior. + Script.setTimeout(function () { + doKick(entityID); + }, KICK_DELAY); + } + + function prePush(side, undo, redo) { + // Stores undo and redo data to include in the next history entry generated for the side. + undoData[side] = undo; + redoData[side] = redo; + } + + function push(side, undo, redo) { + // Add a history entry. + if (side === null) { + side = NO_SIDE; + } + undoData[side] = Object.merge(undoData[side], undo); + redoData[side] = Object.merge(redoData[side], redo); + + // Wipe any redo history after current undo position. + if (undoPosition < history.length - 1) { + history.splice(undoPosition + 1, history.length - undoPosition - 1); + } + + // Limit the number of history items. + if (history.length >= MAX_HISTORY_ITEMS) { + history.splice(0, history.length - MAX_HISTORY_ITEMS + 1); + undoPosition = history.length - 1; + } + + history.push({ undoData: undoData[side], redoData: redoData[side] }); + undoPosition++; + + undoData[side] = {}; + redoData[side] = {}; + } + + function updateEntityIDs(oldEntityID, newEntityID) { + // Replace oldEntityID value with newEntityID in history. + var i, + length; + + function updateEntityIDsInProperty(properties) { + var i, + length; + + if (properties) { + for (i = 0, length = properties.length; i < length; i++) { + if (properties[i].entityID === oldEntityID) { + properties[i].entityID = newEntityID; + } + if (properties[i].properties && properties[i].properties.parentID === oldEntityID) { + properties[i].properties.parentID = newEntityID; + } + } + } + } + + for (i = 0, length = history.length; i < length; i++) { + if (history[i].undoData) { + updateEntityIDsInProperty(history[i].undoData.setProperties); + updateEntityIDsInProperty(history[i].undoData.createEntities); + updateEntityIDsInProperty(history[i].undoData.deleteEntities); + } + if (history[i].redoData) { + updateEntityIDsInProperty(history[i].redoData.setProperties); + updateEntityIDsInProperty(history[i].redoData.createEntities); + updateEntityIDsInProperty(history[i].redoData.deleteEntities); + } + } + } + + function hasUndo() { + return undoPosition > -1; + } + + function hasRedo() { + return undoPosition < history.length - 1; + } + + function undoSetProperties(entityID, properties) { + Entities.editEntity(entityID, properties); + if (properties.gravity) { + kickPhysics(entityID); + } + } + + function undo() { + var undoData, + entityID, + REPEAT_UNDO_DELAY = 500, // ms + i, + length; + + if (undoPosition > -1) { + undoData = history[undoPosition].undoData; + + if (undoData.createEntities) { + for (i = 0, length = undoData.createEntities.length; i < length; i++) { + entityID = Entities.addEntity(undoData.createEntities[i].properties); + updateEntityIDs(undoData.createEntities[i].entityID, entityID); + } + } + + if (undoData.setProperties) { + for (i = 0, length = undoData.setProperties.length; i < length; i++) { + undoSetProperties(undoData.setProperties[i].entityID, undoData.setProperties[i].properties); + if (undoData.setProperties[i].properties.velocity + && Vec3.equal(undoData.setProperties[i].properties.velocity, Vec3.ZERO) + && undoData.setProperties[i].properties.angularVelocity + && Vec3.equal(undoData.setProperties[i].properties.angularVelocity, Vec3.ZERO)) { + // Work around physics bug wherein the entity doesn't always end up at the correct position. + Script.setTimeout( + undoSetProperties(undoData.setProperties[i].entityID, undoData.setProperties[i].properties), + REPEAT_UNDO_DELAY + ); + } + } + } + + if (undoData.deleteEntities) { + for (i = 0, length = undoData.deleteEntities.length; i < length; i++) { + Entities.deleteEntity(undoData.deleteEntities[i].entityID); + } + } + + undoPosition--; + } + } + + function redo() { + var redoData, + entityID, + i, + length; + + + if (undoPosition < history.length - 1) { + redoData = history[undoPosition + 1].redoData; + + if (redoData.createEntities) { + for (i = 0, length = redoData.createEntities.length; i < length; i++) { + entityID = Entities.addEntity(redoData.createEntities[i].properties); + updateEntityIDs(redoData.createEntities[i].entityID, entityID); + } + } + + if (redoData.setProperties) { + for (i = 0, length = redoData.setProperties.length; i < length; i++) { + Entities.editEntity(redoData.setProperties[i].entityID, redoData.setProperties[i].properties); + if (redoData.setProperties[i].properties.gravity) { + kickPhysics(redoData.setProperties[i].entityID); + } + } + } + + if (redoData.deleteEntities) { + for (i = 0, length = redoData.deleteEntities.length; i < length; i++) { + Entities.deleteEntity(redoData.deleteEntities[i].entityID); + } + } + + undoPosition++; + } + } + + return { + prePush: prePush, + push: push, + hasUndo: hasUndo, + hasRedo: hasRedo, + undo: undo, + redo: redo + }; +}()); diff --git a/unpublishedScripts/marketplace/shapes/modules/laser.js b/unpublishedScripts/marketplace/shapes/modules/laser.js new file mode 100644 index 0000000000..1efc38b65a --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/laser.js @@ -0,0 +1,287 @@ +// +// laser.js +// +// Created by David Rowe on 21 Jul 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 +// + +/* global Laser:true */ + +Laser = function (side) { + // Draws hand lasers. + // May intersect with overlays or entities, or bounding box of other hand's selection. + // Laser dot is always drawn on UI entities. + + "use strict"; + + var isLaserEnabled = true, + isLaserOn = false, + + laserLine = null, + laserSphere = null, + + searchDistance = 0.0, + + SEARCH_SPHERE_SIZE = 0.013, // Per farActionGrabEntity.js multiplied by 1.2 per farActionGrabEntity.js. + MINUMUM_SEARCH_SPHERE_SIZE = 0.006, + SEARCH_SPHERE_FOLLOW_RATE = 0.5, + COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }, // Per controllerDispatcherUtils.js. + COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }, // Per controllerDispatcherUtils.js. + COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT, + COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT, + BRIGHT_POW = 0.06, // Per old handControllerGrab.js. + + GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }, // Per HmdDisplayPlugin.cpp and controllers.js. + + PICK_MAX_DISTANCE = 500, // Per controllerDispatcherUtils.js. + PRECISION_PICKING = true, + NO_INCLUDE_IDS = [], + NO_EXCLUDE_IDS = [], + VISIBLE_ONLY = true, + + laserLength, + specifiedLaserLength = null, + laserSphereSize = 0, + + LEFT_HAND = 0, + + uiOverlayIDs = [], + + intersection; + + if (!(this instanceof Laser)) { + return new Laser(side); + } + + function colorPow(color, power) { // Per old handControllerGrab.js. + return { + red: Math.pow(color.red / 255, power) * 255, + green: Math.pow(color.green / 255, power) * 255, + blue: Math.pow(color.blue / 255, power) * 255 + }; + } + + COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_HALF_SQUEEZE, BRIGHT_POW); + COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT = colorPow(COLORS_GRAB_SEARCHING_FULL_SQUEEZE, BRIGHT_POW); + + if (side === LEFT_HAND) { + GRAB_POINT_SPHERE_OFFSET.x = -GRAB_POINT_SPHERE_OFFSET.x; + } + + laserLine = Overlays.addOverlay("line3d", { + lineWidth: 5, + alpha: 1.0, + glow: 1.0, + ignoreRayIntersection: true, + drawInFront: true, + parentID: Uuid.SELF, + parentJointIndex: MyAvatar.getJointIndex(side === LEFT_HAND + ? "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND" + : "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"), + visible: false + }); + laserSphere = Overlays.addOverlay("circle3d", { + innerAlpha: 1.0, + outerAlpha: 0.0, + solid: true, + ignoreRayIntersection: true, + drawInFront: true, + visible: false + }); + + function updateLine(start, end, color) { + Overlays.editOverlay(laserLine, { + start: start, + end: end, + color: color, + visible: true + }); + } + + function updateSphere(location, size, color, brightColor) { + var rotation; + + rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); + + Overlays.editOverlay(laserSphere, { + position: location, + rotation: rotation, + innerColor: brightColor, + outerColor: color, + outerRadius: size, + visible: true + }); + } + + function display(origin, direction, distance, isPressed, isClicked) { + var searchTarget, + sphereSize, + color, + brightColor; + + searchDistance = SEARCH_SPHERE_FOLLOW_RATE * searchDistance + (1.0 - SEARCH_SPHERE_FOLLOW_RATE) * distance; + searchTarget = Vec3.sum(origin, Vec3.multiply(searchDistance, direction)); + sphereSize = Math.max(SEARCH_SPHERE_SIZE * searchDistance, MINUMUM_SEARCH_SPHERE_SIZE); + color = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE : COLORS_GRAB_SEARCHING_HALF_SQUEEZE; + brightColor = isClicked ? COLORS_GRAB_SEARCHING_FULL_SQUEEZE_BRIGHT : COLORS_GRAB_SEARCHING_HALF_SQUEEZE_BRIGHT; + + if (isPressed) { + updateLine(origin, searchTarget, color); + } else { + Overlays.editOverlay(laserLine, { visible: false }); + } + // Avoid flash from large laser sphere when turn on or suddenly increase distance. Rendering seems to update overlay + // position one frame behind so use sphere size from preceding frame. + updateSphere(searchTarget, laserSphereSize, color, brightColor); + laserSphereSize = sphereSize; + } + + function hide() { + Overlays.editOverlay(laserLine, { visible: false }); + Overlays.editOverlay(laserSphere, { visible: false }); + laserSphereSize = 0; + } + + function setUIOverlays(overlayIDs) { + uiOverlayIDs = overlayIDs; + } + + function update(hand) { + var handPosition, + handOrientation, + deltaOrigin, + pickRay; + + if (!isLaserEnabled) { + intersection = {}; + return; + } + + handPosition = hand.position(); + handOrientation = hand.orientation(); + deltaOrigin = Vec3.multiplyQbyV(handOrientation, GRAB_POINT_SPHERE_OFFSET); + pickRay = { + origin: Vec3.sum(handPosition, deltaOrigin), + direction: Quat.getUp(handOrientation), + length: PICK_MAX_DISTANCE + }; + + if (hand.triggerPressed()) { + + // Normal laser operation with trigger. + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (Reticle.pointingAtSystemOverlay || (intersection.overlayID + && [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID].indexOf(intersection.overlayID) !== -1)) { + // No laser if pointing at HUD overlay or tablet; system provides lasers for these cases. + if (isLaserOn) { + isLaserOn = false; + hide(); + } + } else { + if (!intersection.intersects) { + intersection = Entities.findRayIntersection(pickRay, PRECISION_PICKING, NO_INCLUDE_IDS, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + intersection.editableEntity = intersection.intersects && Entities.hasEditableRoot(intersection.entityID); + intersection.overlayID = null; + } + intersection.laserIntersected = intersection.intersects; + laserLength = (specifiedLaserLength !== null) + ? specifiedLaserLength + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + isLaserOn = true; + display(pickRay.origin, pickRay.direction, laserLength, true, hand.triggerClicked()); + } + + } else if (uiOverlayIDs.length > 0) { + + // Special UI cursor. + intersection = Overlays.findRayIntersection(pickRay, PRECISION_PICKING, uiOverlayIDs, NO_EXCLUDE_IDS, + VISIBLE_ONLY); + if (intersection.intersects) { + intersection.laserIntersected = true; + laserLength = (specifiedLaserLength !== null) + ? specifiedLaserLength + : (intersection.intersects ? intersection.distance : PICK_MAX_DISTANCE); + if (!isLaserOn) { + // Start laser dot at UI distance. + searchDistance = laserLength; + } + isLaserOn = true; + display(pickRay.origin, pickRay.direction, laserLength, false, false); + } else if (isLaserOn) { + isLaserOn = false; + hide(); + } + + } else { + intersection = { intersects: false }; + if (isLaserOn) { + isLaserOn = false; + hide(); + } + } + } + + function getIntersection() { + return intersection; + } + + function setLength(length) { + specifiedLaserLength = length; + laserLength = length; + } + + function clearLength() { + specifiedLaserLength = null; + } + + function getLength() { + return laserLength; + } + + function handOffset() { + return GRAB_POINT_SPHERE_OFFSET; + } + + function clear() { + isLaserOn = false; + hide(); + } + + function enable() { + isLaserEnabled = true; + } + + function disable() { + isLaserEnabled = false; + if (isLaserOn) { + hide(); + } + isLaserOn = false; + } + + function destroy() { + Overlays.deleteOverlay(laserLine); + Overlays.deleteOverlay(laserSphere); + } + + return { + setUIOverlays: setUIOverlays, + update: update, + intersection: getIntersection, + setLength: setLength, + clearLength: clearLength, + length: getLength, + enable: enable, + disable: disable, + handOffset: handOffset, + clear: clear, + destroy: destroy + }; +}; + +Laser.prototype = {}; diff --git a/unpublishedScripts/marketplace/shapes/modules/selection.js b/unpublishedScripts/marketplace/shapes/modules/selection.js new file mode 100644 index 0000000000..955cde6bda --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/selection.js @@ -0,0 +1,795 @@ +// +// selection.js +// +// Created by David Rowe on 21 Jul 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 +// + +/* global SelectionManager:true, App, History */ + +SelectionManager = function (side) { + // Manages set of selected entities. Currently supports just one set of linked entities. + + "use strict"; + + var selection = [], // Subset of properties to provide externally. + selectionIDs = [], + selectionProperties = [], // Full set of properties for history. + intersectedEntityID = null, + intersectedEntityIndex, + rootEntityID = null, + rootPosition, + rootOrientation, + scaleFactor, + scaleRootOffset, + scaleRootOrientation, + startPosition, + startOrientation, + isEditing = false, + ENTITY_TYPE = "entity", + ENTITY_TYPES_WITH_COLOR = ["Box", "Sphere", "Shape", "PolyLine"], + ENTITY_TYPES_2D = ["Text", "Web"], + MIN_HISTORY_MOVE_DISTANCE = 0.005, + MIN_HISTORY_ROTATE_ANGLE = 0.017453; // Radians = 1 degree. + + if (!(this instanceof SelectionManager)) { + return new SelectionManager(side); + } + + function traverseEntityTree(id, selection, selectionIDs, selectionProperties) { + // Recursively traverses tree of entities and their children, gather IDs and properties. + // The root entity is always the first entry. + var children, + properties, + i, + length; + + properties = Entities.getEntityProperties(id); + delete properties.entityID; + selection.push({ + id: id, + type: properties.type, + position: properties.position, + parentID: properties.parentID, + localPosition: properties.localPosition, + localRotation: properties.localRotation, + registrationPoint: properties.registrationPoint, + rotation: properties.rotation, + dimensions: properties.dimensions, + dynamic: properties.dynamic, + collisionless: properties.collisionless, + userData: properties.userData + }); + selectionIDs.push(id); + selectionProperties.push({ entityID: id, properties: properties }); + + if (id === intersectedEntityID) { + intersectedEntityIndex = selection.length - 1; + } + + children = Entities.getChildrenIDs(id); + for (i = 0, length = children.length; i < length; i++) { + if (Entities.getNestableType(children[i]) === ENTITY_TYPE) { + traverseEntityTree(children[i], selection, selectionIDs, selectionProperties); + } + } + } + + function select(intersectionEntityID) { + var entityProperties, + PARENT_PROPERTIES = ["parentID", "position", "rotation", "dymamic", "collisionless"]; + + intersectedEntityID = intersectionEntityID; + + // Find root parent. + rootEntityID = Entities.rootOf(intersectedEntityID); + + // Selection position and orientation is that of the root entity. + entityProperties = Entities.getEntityProperties(rootEntityID, PARENT_PROPERTIES); + rootPosition = entityProperties.position; + rootOrientation = entityProperties.rotation; + + // Find all children. + selection = []; + selectionIDs = []; + selectionProperties = []; + traverseEntityTree(rootEntityID, selection, selectionIDs, selectionProperties); + } + + function append(rootEntityID) { + // Add further entities to the selection. + // Assumes that rootEntityID is not already in the selection. + traverseEntityTree(rootEntityID, selection, selectionIDs, selectionProperties); + } + + function getIntersectedEntityID() { + return intersectedEntityID; + } + + function getIntersectedEntityIndex() { + return intersectedEntityIndex; + } + + function getRootEntityID() { + return rootEntityID; + } + + function getSelection() { + return selection; + } + + function contains(entityID) { + return selectionIDs.indexOf(entityID) !== -1; + } + + function count() { + return selection.length; + } + + function getBoundingBox() { + var center, + localCenter, + orientation, + inverseOrientation, + dimensions, + min, + max, + i, + j, + length, + registration, + position, + rotation, + corners = [], + NUM_CORNERS = 8; + + if (selection.length === 1) { + if (Vec3.equal(selection[0].registrationPoint, Vec3.HALF)) { + center = rootPosition; + } else { + center = Vec3.sum(rootPosition, + Vec3.multiplyQbyV(rootOrientation, + Vec3.multiplyVbyV(selection[0].dimensions, + Vec3.subtract(Vec3.HALF, selection[0].registrationPoint)))); + } + localCenter = Vec3.multiplyQbyV(Quat.inverse(rootOrientation), Vec3.subtract(center, rootPosition)); + orientation = rootOrientation; + dimensions = selection[0].dimensions; + } else if (selection.length > 1) { + // Find min & max x, y, z values of entities' dimension box corners in root entity coordinate system. + // Note: Don't use entities' bounding boxes because they're in world coordinates and may make the calculated + // bounding box be larger than necessary. + min = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ZERO, selection[0].registrationPoint), selection[0].dimensions); + max = Vec3.multiplyVbyV(Vec3.subtract(Vec3.ONE, selection[0].registrationPoint), selection[0].dimensions); + inverseOrientation = Quat.inverse(rootOrientation); + for (i = 1, length = selection.length; i < length; i++) { + + registration = selection[i].registrationPoint; + corners = [ + { x: -registration.x, y: -registration.y, z: -registration.z }, + { x: -registration.x, y: -registration.y, z: 1.0 - registration.z }, + { x: -registration.x, y: 1.0 - registration.y, z: -registration.z }, + { x: -registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z }, + { x: 1.0 - registration.x, y: -registration.y, z: -registration.z }, + { x: 1.0 - registration.x, y: -registration.y, z: 1.0 - registration.z }, + { x: 1.0 - registration.x, y: 1.0 - registration.y, z: -registration.z }, + { x: 1.0 - registration.x, y: 1.0 - registration.y, z: 1.0 - registration.z } + ]; + + position = selection[i].position; + rotation = selection[i].rotation; + dimensions = selection[i].dimensions; + + for (j = 0; j < NUM_CORNERS; j++) { + // Corner position in world coordinates. + corners[j] = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiplyVbyV(corners[j], dimensions))); + // Corner position in root entity coordinates. + corners[j] = Vec3.multiplyQbyV(inverseOrientation, Vec3.subtract(corners[j], rootPosition)); + // Update min & max. + min = Vec3.min(corners[j], min); + max = Vec3.max(corners[j], max); + } + } + + // Calculate bounding box. + center = Vec3.sum(rootPosition, + Vec3.multiplyQbyV(rootOrientation, Vec3.multiply(0.5, Vec3.sum(min, max)))); + localCenter = Vec3.multiply(0.5, Vec3.sum(min, max)); + orientation = rootOrientation; + dimensions = Vec3.subtract(max, min); + } + + return { + center: center, + localCenter: localCenter, + orientation: orientation, + dimensions: dimensions + }; + } + + function is2D() { + return selection.length === 1 && ENTITY_TYPES_2D.indexOf(selection[0].type) !== -1; + } + + function doKick(entityID) { + var properties, + NO_KICK_ENTITY_TYPES = ["Text", "Web", "PolyLine", "ParticleEffect"], // Don't respond to gravity so don't kick. + DYNAMIC_VELOCITY_THRESHOLD = 0.05, // See EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + DYNAMIC_VELOCITY_KICK = { x: 0, y: 0.1, z: 0 }; + + if (entityID === rootEntityID && isEditing) { + // Don't kick if have started editing entity again. + return; + } + + properties = Entities.getEntityProperties(entityID, ["type", "velocity", "gravity"]); + if (NO_KICK_ENTITY_TYPES.indexOf(properties.type) === -1 + && Vec3.length(properties.gravity) > 0 && Vec3.length(properties.velocity) < DYNAMIC_VELOCITY_THRESHOLD) { + Entities.editEntity(entityID, { velocity: DYNAMIC_VELOCITY_KICK }); + } + } + + function kickPhysics(entityID) { + // Gives entities a small kick to start off physics, if necessary. + var KICK_DELAY = 750; // ms + + // Give physics a chance to catch up. Avoids some erratic behavior. + Script.setTimeout(function () { + doKick(entityID); + }, KICK_DELAY); + } + + function startEditing() { + var i; + + // Remember start properties for history entry. + startPosition = selection[0].position; + startOrientation = selection[0].rotation; + + // Disable entity set's physics. + for (i = selection.length - 1; i >= 0; i--) { + Entities.editEntity(selection[i].id, { + dynamic: false, // So that gravity doesn't fight with us trying to hold the entity in place. + collisionless: true, // So that entity doesn't bump us about as we resize the entity. + velocity: Vec3.ZERO, // So that entity doesn't drift if we've grabbed a set while it was moving. + angularVelocity: Vec3.ZERO // "" + }); + } + + // Stop moving. + Entities.editEntity(rootEntityID, { velocity: Vec3.ZERO, angularVelocity: Vec3.ZERO }); + + isEditing = true; + } + + function finishEditing() { + var i; + + // Restore entity set's physics. + // Note: Need to apply children-first in order to avoid children's relative positions sometimes drifting. + for (i = selection.length - 1; i >= 0; i--) { + Entities.editEntity(selection[i].id, { + dynamic: selection[i].dynamic, + collisionless: selection[i].collisionless + }); + } + + // Add history entry. + if (selection.length > 0 + && (!Vec3.equal(startPosition, rootPosition) || !Quat.equal(startOrientation, rootOrientation))) { + // Positions and orientations can be identical if change grabbing hands when finish scaling. + History.push( + side, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } + ] + }, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: rootPosition, rotation: rootOrientation } } + ] + } + ); + } + + // Kick off physics if necessary. + if (selection.length > 0 && selection[0].dynamic) { + kickPhysics(selection[0].id); + } + + isEditing = false; + } + + function getPositionAndOrientation() { + // Position and orientation of root entity. + return { + position: rootPosition, + orientation: rootOrientation + }; + } + + function setPositionAndOrientation(position, orientation) { + // Position and orientation of root entity. + rootPosition = position; + rootOrientation = orientation; + Entities.editEntity(rootEntityID, { + position: position, + rotation: orientation + }); + } + + function startDirectScaling(center) { + // Save initial position and orientation so that can scale relative to these without accumulating float errors. + scaleRootOffset = Vec3.subtract(rootPosition, center); + scaleRootOrientation = rootOrientation; + + // User is grabbing entity; add a history entry for movement up until the start of scaling and update start position and + // orientation; unless very small movement. + if (Vec3.distance(startPosition, rootPosition) >= MIN_HISTORY_MOVE_DISTANCE + || Quat.rotationBetween(startOrientation, rootOrientation) >= MIN_HISTORY_ROTATE_ANGLE) { + History.push( + side, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } + ] + }, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: rootPosition, rotation: rootOrientation } } + ] + } + ); + startPosition = rootPosition; + startOrientation = rootOrientation; + } + } + + function directScale(factor, rotation, center) { + // Scale, position, and rotate selection. + // We can get away with scaling the z size of 2D entities - incongruities are barely noticeable and things recover. + var i, + length; + + // Scale, position, and orient root. + rootPosition = Vec3.sum(center, Vec3.multiply(factor, Vec3.multiplyQbyV(rotation, scaleRootOffset))); + rootOrientation = Quat.multiply(rotation, scaleRootOrientation); + Entities.editEntity(selection[0].id, { + dimensions: Vec3.multiply(factor, selection[0].dimensions), + position: rootPosition, + rotation: rootOrientation + }); + + // Scale and position children. + for (i = 1, length = selection.length; i < length; i++) { + Entities.editEntity(selection[i].id, { + dimensions: Vec3.multiply(factor, selection[i].dimensions), + localPosition: Vec3.multiply(factor, selection[i].localPosition), + localRotation: selection[i].localRotation // Always specify localRotation otherwise rotations can drift. + }); + } + + // Save most recent scale factor. + scaleFactor = factor; + } + + function finishDirectScaling() { + // Update selection with final entity properties. + var undoData = [], + redoData = [], + i, + length; + + // Final scale, position, and orientation of root. + undoData.push({ + entityID: selection[0].id, + properties: { + dimensions: selection[0].dimensions, + position: startPosition, + rotation: startOrientation + } + }); + selection[0].dimensions = Vec3.multiply(scaleFactor, selection[0].dimensions); + selection[0].position = rootPosition; + selection[0].rotation = rootOrientation; + redoData.push({ + entityID: selection[0].id, + properties: { + dimensions: selection[0].dimensions, + position: selection[0].position, + rotation: selection[0].rotation + } + }); + + // Final scale and position of children. + for (i = 1, length = selection.length; i < length; i++) { + undoData.push({ + entityID: selection[i].id, + properties: { + dimensions: selection[i].dimensions, + localPosition: selection[i].localPosition, + localRotation: selection[i].localRotation + } + }); + selection[i].dimensions = Vec3.multiply(scaleFactor, selection[i].dimensions); + selection[i].localPosition = Vec3.multiply(scaleFactor, selection[i].localPosition); + redoData.push({ + entityID: selection[i].id, + properties: { + dimensions: selection[i].dimensions, + localPosition: selection[i].localPosition, + localRotation: selection[i].localRotation + } + }); + } + + // Add history entry. + History.push(side, { setProperties: undoData }, { setProperties: redoData }); + + // Update grab start data for its undo. + startPosition = rootPosition; + startOrientation = rootOrientation; + } + + function startHandleScaling(position) { + // Save initial offset from hand position to root position so that can scale without accumulating float errors. + scaleRootOffset = Vec3.multiplyQbyV(Quat.inverse(rootOrientation), Vec3.subtract(rootPosition, position)); + + // User is grabbing entity; add a history entry for movement up until the start of scaling and update start position and + // orientation; unless very small movement. + if (Vec3.distance(startPosition, rootPosition) >= MIN_HISTORY_MOVE_DISTANCE + || Quat.rotationBetween(startOrientation, rootOrientation) >= MIN_HISTORY_ROTATE_ANGLE) { + History.push( + side, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: startPosition, rotation: startOrientation } } + ] + }, + { + setProperties: [ + { entityID: rootEntityID, properties: { position: rootPosition, rotation: rootOrientation } } + ] + } + ); + startPosition = rootPosition; + startOrientation = rootOrientation; + } + } + + function handleScale(factor, position, orientation) { + // Scale and reposition and orient selection. + // We can get away with scaling the z size of 2D entities - incongruities are barely noticeable and things recover. + var i, + length; + + // Scale and position root. + rootPosition = Vec3.sum(Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(factor, scaleRootOffset)), position); + rootOrientation = orientation; + Entities.editEntity(selection[0].id, { + dimensions: Vec3.multiplyVbyV(factor, selection[0].dimensions), + position: rootPosition, + rotation: rootOrientation + }); + + // Scale and position children. + // Only corner handles are used for scaling multiple entities so scale factor is the same in all dimensions. + // Therefore don't need to take into account orientation relative to parent when scaling local position. + for (i = 1, length = selection.length; i < length; i++) { + Entities.editEntity(selection[i].id, { + dimensions: Vec3.multiplyVbyV(factor, selection[i].dimensions), + localPosition: Vec3.multiplyVbyV(factor, selection[i].localPosition), + localRotation: selection[i].localRotation // Always specify localRotation otherwise rotations can drift. + }); + } + + // Save most recent scale factor. + scaleFactor = factor; + } + + function finishHandleScaling() { + // Update selection with final entity properties. + var undoData = [], + redoData = [], + i, + length; + + // Final scale and position of root. + undoData.push({ + entityID: selection[0].id, + properties: { + dimensions: selection[0].dimensions, + position: startPosition, + rotation: startOrientation + } + }); + selection[0].dimensions = Vec3.multiplyVbyV(scaleFactor, selection[0].dimensions); + selection[0].position = rootPosition; + selection[0].rotation = rootOrientation; + redoData.push({ + entityID: selection[0].id, + properties: { + dimensions: selection[0].dimensions, + position: selection[0].position, + rotation: selection[0].rotation + } + }); + + // Final scale and position of children. + for (i = 1, length = selection.length; i < length; i++) { + undoData.push({ + entityID: selection[i].id, + properties: { + dimensions: selection[i].dimensions, + localPosition: selection[i].localPosition, + localRotation: selection[i].localRotation + } + }); + selection[i].dimensions = Vec3.multiplyVbyV(scaleFactor, selection[i].dimensions); + selection[i].localPosition = Vec3.multiplyVbyV(scaleFactor, selection[i].localPosition); + redoData.push({ + entityID: selection[i].id, + properties: { + dimensions: selection[i].dimensions, + localPosition: selection[i].localPosition, + localRotation: selection[i].localRotation + } + }); + } + + // Add history entry. + History.push(side, { setProperties: undoData }, { setProperties: redoData }); + + // Update grab start data for its undo. + startPosition = rootPosition; + startOrientation = rootOrientation; + } + + function cloneEntities() { + var parentIDIndexes = [], + intersectedEntityIndex = 0, + parentID, + properties, + undoData = [], + redoData = [], + i, + j, + length; + + // Map parent IDs; find intersectedEntityID's index. + for (i = 1, length = selection.length; i < length; i++) { + if (selection[i].id === intersectedEntityID) { + intersectedEntityIndex = i; + } + parentID = selection[i].parentID; + for (j = 0; j < i; j++) { + if (parentID === selection[j].id) { + parentIDIndexes[i] = j; + break; + } + } + } + + // Clone entities. + for (i = 0, length = selection.length; i < length; i++) { + properties = Entities.getEntityProperties(selection[i].id); + if (i > 0) { + properties.parentID = selection[parentIDIndexes[i]].id; + } + selection[i].id = Entities.addEntity(properties); + undoData.push({ entityID: selection[i].id }); + redoData.push({ entityID: selection[i].id, properties: properties }); + } + + // Update selection info. + intersectedEntityID = selection[intersectedEntityIndex].id; + rootEntityID = selection[0].id; + + // Add history entry. + History.prePush(side, { deleteEntities: undoData }, { createEntities: redoData }); + } + + function applyColor(color, isApplyToAll) { + // Entities without a color property simply ignore the edit. + var properties, + isOK = false, + undoData = [], + redoData = [], + i, + length; + + if (isApplyToAll) { + for (i = 0, length = selection.length; i < length; i++) { + properties = Entities.getEntityProperties(selection[i].id, ["type", "color"]); + if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) !== -1) { + Entities.editEntity(selection[i].id, { + color: color + }); + undoData.push({ entityID: intersectedEntityID, properties: { color: properties.color } }); + redoData.push({ entityID: intersectedEntityID, properties: { color: color } }); + isOK = true; + } + } + if (undoData.length > 0) { + History.push(side, { setProperties: undoData }, { setProperties: redoData }); + } + } else { + properties = Entities.getEntityProperties(intersectedEntityID, ["type", "color"]); + if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) !== -1) { + Entities.editEntity(intersectedEntityID, { + color: color + }); + History.push( + side, + { setProperties: [{ entityID: intersectedEntityID, properties: { color: properties.color } }] }, + { setProperties: [{ entityID: intersectedEntityID, properties: { color: color } }] } + ); + isOK = true; + } + } + + return isOK; + } + + function getColor(entityID) { + var properties; + + properties = Entities.getEntityProperties(entityID, "color"); + if (ENTITY_TYPES_WITH_COLOR.indexOf(properties.type) === -1) { + // Some entities don't have a color property. + return null; + } + + return properties.color; + } + + function updatePhysicsUserData(userDataString, physicsUserData) { + var userData = {}; + + if (userDataString !== "") { + try { + userData = JSON.parse(userDataString); + } catch (e) { + App.log(side, "ERROR: Invalid userData in entity being updated! " + userDataString); + } + } + + if (!userData.hasOwnProperty("grabbableKey")) { + userData.grabbableKey = {}; + } + userData.grabbableKey.grabbable = physicsUserData.grabbableKey.grabbable; + + return JSON.stringify(userData); + } + + function applyPhysics(physicsProperties) { + // Regarding trees of entities, when physics is to be enabled the physics engine currently: + // - Only works with physics applied to the root entity; i.e., child entities are ignored for collisions. + // - Requires child entities to be dynamic if the root entity is dynamic, otherwise child entities can drift. + // - Requires child entities to be collisionless, otherwise the entity tree can become self-propelled. + // See also: Groups.group() and ungroup(). + var properties, + property, + undoData = [], + redoData = [], + i, + length; + + // Make children cater to physicsProperties. + properties = { + dynamic: physicsProperties.dynamic, + collisionless: physicsProperties.dynamic || physicsProperties.collisionless + }; + for (i = 1, length = selection.length; i < length; i++) { + undoData.push({ + entityID: selection[i].id, + properties: { + dynamic: selection[i].dynamic, + collisionless: selection[i].collisionless + } + }); + Entities.editEntity(selection[i].id, properties); + undoData.push({ + entityID: selection[i].id, + properties: properties + }); + } + + // Undo data. + properties = { + position: selection[0].position, + rotation: selection[0].rotation, + velocity: Vec3.ZERO, + angularVelocity: Vec3.ZERO + }; + for (property in physicsProperties) { + if (physicsProperties.hasOwnProperty(property)) { + properties[property] = selectionProperties[0].properties[property]; + } + } + if (properties.userData === undefined) { + properties.userData = ""; + } + undoData.push({ + entityID: selection[0].id, + properties: properties + }); + + // Set root per physicsProperties. + properties = Object.clone(physicsProperties); + properties.userData = updatePhysicsUserData(selection[intersectedEntityIndex].userData, physicsProperties.userData); + Entities.editEntity(rootEntityID, properties); + + // Redo data. + properties.position = selection[0].position; + properties.rotation = selection[0].rotation; + properties.velocity = Vec3.ZERO; + properties.angularVelocity = Vec3.ZERO; + redoData.push({ + entityID: selection[0].id, + properties: properties + }); + + // Add history entry. + History.push(side, { setProperties: undoData }, { setProperties: redoData }); + + // Kick off physics if necessary. + if (physicsProperties.dynamic) { + kickPhysics(rootEntityID); + } + } + + function clear() { + selection = []; + intersectedEntityID = null; + rootEntityID = null; + } + + function deleteEntities() { + if (rootEntityID) { + History.push(side, { createEntities: selectionProperties }, { deleteEntities: [{ entityID: rootEntityID }] }); + Entities.deleteEntity(rootEntityID); // Children are automatically deleted. + clear(); + } + } + + function destroy() { + clear(); + } + + return { + select: select, + append: append, + selection: getSelection, + contains: contains, + count: count, + intersectedEntityID: getIntersectedEntityID, + intersectedEntityIndex: getIntersectedEntityIndex, + rootEntityID: getRootEntityID, + boundingBox: getBoundingBox, + is2D: is2D, + getPositionAndOrientation: getPositionAndOrientation, + setPositionAndOrientation: setPositionAndOrientation, + startEditing: startEditing, + startDirectScaling: startDirectScaling, + directScale: directScale, + finishDirectScaling: finishDirectScaling, + startHandleScaling: startHandleScaling, + handleScale: handleScale, + finishHandleScaling: finishHandleScaling, + finishEditing: finishEditing, + cloneEntities: cloneEntities, + applyColor: applyColor, + getColor: getColor, + applyPhysics: applyPhysics, + deleteEntities: deleteEntities, + clear: clear, + destroy: destroy + }; +}; + +SelectionManager.prototype = {}; diff --git a/unpublishedScripts/marketplace/shapes/modules/toolIcon.js b/unpublishedScripts/marketplace/shapes/modules/toolIcon.js new file mode 100644 index 0000000000..1571a6b037 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/toolIcon.js @@ -0,0 +1,164 @@ +// +// toolIcon.js +// +// Created by David Rowe on 28 Jul 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 +// + +/* global ToolIcon:true, App, UIT */ + +ToolIcon = function (side) { + // Tool icon displayed on non-dominant hand. + + "use strict"; + + var LEFT_HAND = 0, + + MODEL_DIMENSIONS = { x: 0.1944, y: 0.1928, z: 0.1928 }, // Raw FBX dimensions. + MODEL_SCALE = 0.7, // Adjust icon dimensions so that the green bar matches that of the Tools header. + MODEL_POSITION_LEFT_HAND = { x: -0.025, y: 0.03, z: 0 }, // x raises in thumb direction; y moves in fingers direction. + MODEL_POSITION_RIGHT_HAND = { x: 0.025, y: 0.03, z: 0 }, // "" + MODEL_ROTATION_LEFT_HAND = Quat.fromVec3Degrees({ x: 0, y: 0, z: 100 }), + MODEL_ROTATION_RIGHT_HAND = Quat.fromVec3Degrees({ x: 0, y: 180, z: -100 }), + + MODEL_TYPE = "model", + MODEL_PROPERTIES = { + url: Script.resolvePath("../assets/tools/tool-icon.fbx"), + dimensions: Vec3.multiply(MODEL_SCALE, MODEL_DIMENSIONS), + solid: true, + alpha: 1.0, + parentID: Uuid.SELF, + ignoreRayIntersection: true, + visible: true + }, + + IMAGE_TYPE = "image3d", + IMAGE_PROPERTIES = { + localRotation: Quat.fromVec3Degrees({ x: -90, y: -90, z: 0 }), + alpha: 1.0, + emissive: true, + ignoreRayIntersection: true, + isFacingAvatar: false, + visible: true + }, + + ICON_PROPERTIES = { + // Relative to model overlay. x is in fingers direction; y is in thumb direction. + localPosition: { x: 0.020, y: 0.069, z: 0 }, + color: UIT.colors.lightGrayText + }, + LABEL_PROPERTIES = { + localPosition: { x: -0.040, y: 0.067, z: 0 }, + color: UIT.colors.white + }, + SUBLABEL_PROPERTIES = { + localPosition: { x: -0.055, y: 0.067, z: 0 }, + color: UIT.colors.lightGrayText + }, + + ICON_SCALE_FACTOR = 3.0, + LABEL_SCALE_FACTOR = 1.8, + + handJointName, + localPosition, + localRotation, + + modelOverlay = null; + + if (!(this instanceof ToolIcon)) { + return new ToolIcon(); + } + + function setHand(side) { + // Assumes UI is not displaying. + if (side === LEFT_HAND) { + handJointName = "LeftHand"; + localPosition = MODEL_POSITION_LEFT_HAND; + localRotation = MODEL_ROTATION_LEFT_HAND; + } else { + handJointName = "RightHand"; + localPosition = MODEL_POSITION_RIGHT_HAND; + localRotation = MODEL_ROTATION_RIGHT_HAND; + } + } + + setHand(side); + + function clear() { + // Deletes current tool model. + if (modelOverlay) { + Overlays.deleteOverlay(modelOverlay); // Child overlays are automatically deleted. + modelOverlay = null; + } + } + + function display(iconInfo) { + // Displays icon on hand. + var handJointIndex, + properties; + + handJointIndex = MyAvatar.getJointIndex(handJointName); + if (handJointIndex === -1) { + // Don't display if joint isn't available (yet) to attach to. + // User can clear this condition by toggling the app off and back on once avatar finishes loading. + App.log(side, "ERROR: ToolIcon: Hand joint index isn't available!"); + return; + } + + if (modelOverlay !== null) { + // Should never happen because tool needs to be cleared in order for user to return to Tools menu. + clear(); + } + + // Model. + properties = Object.clone(MODEL_PROPERTIES); + properties.parentJointIndex = handJointIndex; + properties.localPosition = localPosition; + properties.localRotation = localRotation; + modelOverlay = Overlays.addOverlay(MODEL_TYPE, properties); + + // Icon. + properties = Object.clone(IMAGE_PROPERTIES); + properties = Object.merge(properties, ICON_PROPERTIES); + properties.parentID = modelOverlay; + properties.url = iconInfo.icon.properties.url; + properties.dimensions = { + x: ICON_SCALE_FACTOR * iconInfo.icon.properties.dimensions.x, + y: ICON_SCALE_FACTOR * iconInfo.icon.properties.dimensions.y + }; + properties.localPosition.y += ICON_SCALE_FACTOR * iconInfo.icon.headerOffset.y; + Overlays.addOverlay(IMAGE_TYPE, properties); + + // Label. + properties = Object.clone(IMAGE_PROPERTIES); + properties = Object.merge(properties, LABEL_PROPERTIES); + properties.parentID = modelOverlay; + properties.url = iconInfo.label.properties.url; + properties.scale = LABEL_SCALE_FACTOR * iconInfo.label.properties.scale; + Overlays.addOverlay(IMAGE_TYPE, properties); + + // Sublabel. + properties = Object.clone(IMAGE_PROPERTIES); + properties = Object.merge(properties, SUBLABEL_PROPERTIES); + properties.parentID = modelOverlay; + properties.url = iconInfo.sublabel.properties.url; + properties.scale = LABEL_SCALE_FACTOR * iconInfo.sublabel.properties.scale; + Overlays.addOverlay(IMAGE_TYPE, properties); + } + + function destroy() { + clear(); + } + + return { + setHand: setHand, + display: display, + clear: clear, + destroy: destroy + }; +}; + +ToolIcon.prototype = {}; diff --git a/unpublishedScripts/marketplace/shapes/modules/toolsMenu.js b/unpublishedScripts/marketplace/shapes/modules/toolsMenu.js new file mode 100644 index 0000000000..19c114c8e9 --- /dev/null +++ b/unpublishedScripts/marketplace/shapes/modules/toolsMenu.js @@ -0,0 +1,3694 @@ +// +// toolsMenu.js +// +// Created by David Rowe on 22 Jul 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 +// + +/* global ToolsMenu: true, App, Feedback, UIT */ + +ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) { + // Tool menu displayed on top of forearm. + + "use strict"; + + var attachmentJointName, + + menuOriginLocalPosition, + menuOriginLocalRotation, + + menuOriginOverlay, + menuHeaderOverlay, + menuHeaderHeadingOverlay, + menuHeaderBarOverlay, + menuHeaderBackOverlay, + menuHeaderTitleOverlay, + menuHeaderIconOverlay, + menuPanelOverlay, + + menuOverlays = [], + menuHoverOverlays = [], + menuIconOverlays = [], + menuLabelOverlays = [], + menuEnabled = [], + + optionsOverlays = [], + optionsOverlaysIDs = [], // Text ids (names) of options overlays. + optionsOverlaysLabels = [], // Overlay IDs of labels for optionsOverlays. + optionsOverlaysSublabels = [], // Overlay IDs of sublabels for optionsOverlays. + optionsSliderData = [], // Uses same index values as optionsOverlays. + optionsColorData = [], // Uses same index values as optionsOverlays. + optionsExtraOverlays = [], + optionsEnabled = [], + optionsSettings = { + //