diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index e6b3392aad..6b78826d75 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -12,10 +12,8 @@ function(JOIN VALUES GLUE OUTPUT) endfunction() -if (NOT DEV_BUILD) - set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc) - generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg) -endif() +set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc) +generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg) # set a default root dir for each of our optional externals if it was not passed set(OPTIONAL_EXTERNALS "LeapMotion") @@ -74,9 +72,7 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}") # add them to the interface source files set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") -if (NOT DEV_BUILD) list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC}) -endif() if (UNIX) install( diff --git a/interface/resources/qml/CurrentAPI.qml b/interface/resources/qml/CurrentAPI.qml new file mode 100644 index 0000000000..45cf09925a --- /dev/null +++ b/interface/resources/qml/CurrentAPI.qml @@ -0,0 +1,642 @@ +// +// ScriptAPI.qml +// +// Created by Luis Cuenca on 12/18/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "styles-uit" +import "controls-uit" as HifiControls + +Item { + id: root + width: parent.width + height: parent.height + + property var hideQtMethods: true + + property var maxUpdateValues: 20 + property var maxReloadValues: 200 + property var apiMembers: [] + property var membersWithValues: [] + property var isReloading: false + property var evaluatingIdx: -1 + property Component scrollSlider + property Component keyboard + + Rectangle { + color: "white" + width: parent.width + height: parent.height + } + + FontLoader { id: ralewayRegular; source: pathToFonts + "fonts/Raleway-Regular.ttf"; } + + Timer { + id: updateList + interval: 200 + repeat: false + running: false + onTriggered: { + scrollSlider.y = 0; + list.contentY = 0; + } + } + + Row { + id: topBar + anchors.left: parent.left + anchors.leftMargin: 8 + width: parent.width + height: 50 + HifiControls.GlyphButton { + id: search + enabled: true + glyph: hifi.glyphs.search + color: hifi.colors.text + size: 48 + width: 50 + height: 50 + onClicked: { + addListElements(searchBar.text); + focus = true; + } + } + + HifiControls.GlyphButton { + id: back; + enabled: true; + glyph: hifi.glyphs.backward + color: hifi.colors.text + size: 48 + width: 30 + height: 50 + anchors.margins: 2 + onClicked: { + var text = searchBar.text; + var chain = text.split("."); + if (chain.length > 2) { + var result = chain[0]+"."; + for (var i = 1; i < chain.length-2; i++) { + result += chain[i] + "."; + } + result += chain[chain.length-2]; + searchBar.text = result; + } else { + searchBar.text = (chain.length > 1) ? chain[0] : ""; + } + if (chain.length > 1) { + addListElements(searchBar.text); + } else { + addListElements(); + } + focus = true; + } + } + + TextField { + id: searchBar + focus: true + font.pixelSize: 16 + width: 2*(parent.width-back.width-search.width-reload.width-update.width-evaluate.width-addMember.width-16)/3 + height: parent.height + font.family: ralewayRegular.name + placeholderText: "Search" + onAccepted: { + console.log("Enter Pressed"); + search.clicked(); + } + onActiveFocusChanged: { + if (activeFocus && HMD.mounted) { + keyboard.raised = true; + } else { + keyboard.raised = false; + } + } + + } + + HifiControls.Button { + id: addMember; + enabled: true; + text: "+" + width: 50 + height: 50 + anchors.margins: 2 + onClicked: { + addNewMember(); + updateList.start(); + focus = true; + } + } + + HifiControls.Button { + id: evaluate; + enabled: true; + text: "Eval" + width: 50 + height: 50 + anchors.margins: 2 + onClicked: { + evaluateMember(); + focus = true; + } + } + TextField { + id: valueBar + focus: true + font.pixelSize: 16 + width: (parent.width-back.width-search.width-reload.width-update.width-evaluate.width-addMember.width-16)/3 + height: parent.height + font.family: ralewayRegular.name + placeholderText: "Value" + textColor: "#4466DD" + anchors.margins: 2 + } + + HifiControls.GlyphButton { + id: reload; + enabled: false; + glyph: hifi.glyphs.reload + color: hifi.colors.text + size: 48 + width: 50 + height: 50 + anchors.margins: 2 + onClicked: { + reloadListValues(); + focus = true; + } + } + + HifiControls.GlyphButton { + id: update; + enabled: false; + glyph: hifi.glyphs.playback_play + size: 48 + width: 50 + height: 50 + anchors.margins: 2 + onClicked: { + if (isReloading) { + update.glyph = hifi.glyphs.playback_play + isReloading = false; + stopReload(); + } else { + update.glyph = hifi.glyphs.stop_square + isReloading = true; + startReload(); + } + focus = true; + } + } + } + + ListModel { + id: memberModel + } + + Component { + id: memberDelegate + + Row { + id: memberRow + property var isMainKey: apiType === "class"; + spacing: 10 + Rectangle { + width: isMainKey ? 20 : 40; + height: parent.height + } + + RalewayRegular { + text: apiMember + size: !isMainKey ? 16 : 22 + MouseArea { + width: list.width + height: parent.height + onClicked: { + searchBar.text = apiType=="function()" ? apiMember + "()" : apiMember; + valueBar.text = !apiValue ? "" : apiValue; + list.currentIndex = index; + evaluatingIdx = index; + } + onDoubleClicked: { + if (apiType === "class") { + addListElements(apiMember+"."); + } else { + isolateElement(evaluatingIdx); + } + + } + } + } + + + RalewayRegular { + text: apiType + size: 14 + color: hifi.colors.baseGrayHighlight + } + + RalewayRegular { + text: !apiValue ? "" : apiValue; + size: 16 + color: "#4466DD" + } + } + } + + + Rectangle { + id: membersBackground + anchors { + left: parent.left; right: parent.right; top: topBar.bottom; bottom: parent.bottom; + margins: hifi.dimensions.contentMargin.x + bottomMargin: hifi.dimensions.contentSpacing.y + 40 + } + color: "white" + radius: 4 + + ListView { + id: list + anchors { + top: parent.top + left: parent.left + right: scrollBar.left + bottom: parent.bottom + margins: 4 + } + clip: true + cacheBuffer: 4000 + model: memberModel + delegate: memberDelegate + highlightMoveDuration: 0 + + highlight: Rectangle { + anchors { + left: parent ? parent.left : undefined + right: parent ? parent.right : undefined + leftMargin: hifi.dimensions.borderWidth + rightMargin: hifi.dimensions.borderWidth + } + color: "#BBDDFF" + } + onMovementStarted: { + scrollSlider.manual = true; + } + onMovementEnded: { + if (list.contentHeight > list.height) { + var range = list.contentY/(list.contentHeight-list.height); + range = range > 1 ? 1 : range; + var idx = Math.round((list.count-1)*range); + scrollSlider.positionSlider(idx); + } + scrollSlider.manual = false; + returnToBounds() + } + } + + Rectangle { + id: scrollBar + + property bool scrolling: list.contentHeight > list.height + + anchors { + top: parent.top + right: parent.right + bottom: parent.bottom + topMargin: 4 + bottomMargin: 4 + } + width: scrolling ? 18 : 0 + radius: 4 + color: hifi.colors.baseGrayShadow + + MouseArea { + anchors.fill: parent + + onClicked: { + var index = scrollSlider.y * (list.count - 1) / (scrollBar.height - scrollSlider.height); + index = Math.round(index); + var scrollAmount = Math.round(list.count/10); + index = index + (mouse.y <= scrollSlider.y ? -scrollAmount : scrollAmount); + if (index < 0) { + index = 0; + } + if (index > list.count - 1) { + index = list.count - 1; + } + scrollSlider.positionSlider(index); + } + } + + Rectangle { + id: scrollSlider + + property var manual: false + + function positionSlider(index){ + y = index*(scrollBar.height - scrollSlider.height)/(list.count - 1); + } + + anchors { + right: parent.right + rightMargin: 3 + } + width: 12 + height: (list.height / list.contentHeight) * list.height + radius: width / 4 + color: "white" + + visible: scrollBar.scrolling; + + onYChanged: { + var index = y * (list.count - 1) / (scrollBar.height - scrollSlider.height); + index = Math.round(index); + if (!manual) { + list.positionViewAtIndex(index, ListView.Visible); + } + } + + MouseArea { + anchors.fill: parent + drag.target: scrollSlider + drag.axis: Drag.YAxis + drag.minimumY: 0 + drag.maximumY: scrollBar.height - scrollSlider.height + } + } + } + } + + HifiControls.GlyphButton { + id: clipboard; + enabled: true; + glyph: hifi.glyphs.scriptNew + size: 38 + width: 50 + height: 50 + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: 2 + anchors.leftMargin: 8 + onClicked: { + var buffer = ""; + for (var i = 0; i < memberModel.count; i++) { + var datarow = memberModel.get(i); + buffer += "\n" + datarow.apiMember + " " + datarow.apiType + " " + datarow.apiValue; + } + Window.copyToClipboard(buffer); + focus = true; + } + } + + HifiControls.Button { + id: debug; + enabled: true; + text: "Debug Script" + width: 120 + height: 50 + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 2 + anchors.rightMargin: 8 + onClicked: { + sendToScript({type: "selectScript"}); + } + } + + HifiControls.CheckBox { + id: hideQt + boxSize: 25 + boxRadius: 3 + checked: true + anchors.left: clipboard.right + anchors.leftMargin: 8 + anchors.verticalCenter: clipboard.verticalCenter + anchors.margins: 2 + onClicked: { + hideQtMethods = checked; + addListElements(); + } + } + + HifiControls.Label { + id: hideLabel + anchors.left: hideQt.right + anchors.verticalCenter: clipboard.verticalCenter + anchors.margins: 2 + font.pixelSize: 15 + text: "Hide Qt Methods" + } + + HifiControls.Keyboard { + id: keyboard; + raised: false; + anchors { + bottom: parent.bottom; + left: parent.left; + right: parent.right; + } + + Keys.onPressed: { + console.log(event.nativeScanCode); + if (event.key == Qt.Key_Left) { + keyboard.raised = false; + } + } + } + + function addNewMember() { + apiMembers.push({member: searchBar.text, type: "user", value: valueBar.text, hasValue: true}); + var data = {'memberIndex': apiMembers.length-1, 'apiMember': searchBar.text, 'apiType':"user", 'apiValue': valueBar.text}; + memberModel.insert(0, data); + computeMembersWithValues(); + } + + function evaluateMember() { + sendToScript({type: "evaluateMember", data:{member: searchBar.text, index: evaluatingIdx}}); + } + + function getValuesToRefresh() { + var valuesToRefresh = []; + for (var i = 0; i < membersWithValues.length; i++) { + var index = membersWithValues[i]; + var row = memberModel.get(index); + valuesToRefresh.push({index: index, member: (row.apiType == "function()") ? row.apiMember+"()" : row.apiMember, value: row.apiValue}); + } + return valuesToRefresh; + } + + + function reloadListValues(){ + var valuesToRefresh = getValuesToRefresh(); + sendToScript({type: "refreshValues", data:valuesToRefresh}); + } + + function startReload(){ + var valuesToRefresh = getValuesToRefresh(); + sendToScript({type: "startRefreshValues", data:valuesToRefresh}); + } + + function stopReload(){ + sendToScript({type: "stopRefreshValues"}); + } + + function refreshValues(data) { + var buffer = ""; + for (var i = 0; i < data.length; i++) { + var row = memberModel.get(data[i].index); + row.apiValue = data[i].value; + apiMembers[row.memberIndex].value = data[i].value; + memberModel.set(data[i].index, row); + buffer += "\n" + apiMembers[row.memberIndex].member + " : " + data[i].value; + } + print(buffer); + } + + + function fromScript(message) { + if (message.type === "methods") { + apiMembers = message.data; + if (ScriptDiscoveryService.debugScriptUrl != "") { + addListElements("GlobalDebugger"); + if (memberModel.count == 0) { + addListElements(); + } + } else { + addListElements(); + } + + } else if (message.type === "debugMethods") { + addListElements("GlobalDebugger"); + } else if (message.type === "refreshValues") { + refreshValues(message.data); + } else if (message.type === "evaluateMember") { + valueBar.text = message.data.value; + var selrow = memberModel.get(message.data.index); + if (selrow.apiMember === searchBar.text || selrow.apiMember === searchBar.text + "()") { + selrow.apiValue = message.data.value; + apiMembers[selrow.memberIndex].value = message.data.value; + apiMembers[selrow.memberIndex].hasValue = true; + memberModel.set(message.data.index, selrow); + } + } else if (message.type === "selectScript") { + if (message.data.length > 0) { + ScriptDiscoveryService.debugScriptUrl = message.data; + } + } + } + + + function getFilterPairs(filter){ + var filteredArray = []; + var filterChain; + filterChain = filter.split(" "); + for (var i = 0; i < filterChain.length; i++) { + filterChain[i] = filterChain[i].toUpperCase(); + } + var matchPairs = []; + + for (var i = 0; i < apiMembers.length; i++) { + if (filterChain != undefined) { + var found = 0; + var memberComp = apiMembers[i].member.toUpperCase(); + for (var j = 0; j < filterChain.length; j++) { + found += memberComp.indexOf(filterChain[j]) >= 0 ? 1 : 0; + } + if (found === 0) { + continue; + } + matchPairs.push({index: i, found: found, member: apiMembers[i].member}); + } + } + + matchPairs.sort(function(a, b){ + if(a.found > b.found) return -1; + if(a.found < b.found) return 1; + if(a.member > b.member) return 1; + if(a.member < b.member) return -1; + return 0; + }); + + return matchPairs; + } + + function isolateElement(index) { + var oldElement = memberModel.get(index); + var newElement = {memberIndex: oldElement.memberIndex, apiMember: oldElement.apiMember, apiType: oldElement.apiType, apiValue: oldElement.apiValue}; + membersWithValues = apiMembers[oldElement.memberIndex].hasValue ? [0] : []; + memberModel.remove(0, memberModel.count); + memberModel.append(newElement); + } + + function computeMembersWithValues() { + membersWithValues = []; + for (var i = 0; i < memberModel.count; i++) { + var idx = memberModel.get(i).memberIndex; + if (apiMembers[idx].hasValue) { + membersWithValues.push(i); + } + } + update.enabled = membersWithValues.length <= maxUpdateValues; + reload.enabled = membersWithValues.length <= maxReloadValues; + } + + function addListElements(filter) { + valueBar.text = ""; + memberModel.remove(0, memberModel.count); + + var filteredArray = (filter != undefined) ? [] : apiMembers; + var matchPairs; + if (filter != undefined) { + matchPairs = getFilterPairs(filter); + for (var i = 0; i < matchPairs.length; i++) { + if (matchPairs[i].found < matchPairs[0].found) { + break; + } + var idx = matchPairs[i].index; + filteredArray.push(apiMembers[idx]); + } + } + + for (var i = 0; i < filteredArray.length; i++) { + var data = {'memberIndex': matchPairs ? matchPairs[i].index : i, + 'apiMember': filteredArray[i].member, + 'apiType': filteredArray[i].type, + 'apiValue': filteredArray[i].value}; + + if (hideQtMethods) { + var chain = data.apiMember.split("."); + var method = chain[chain.length-1]; + if (method != "destroyed" && + method != "objectName" && + method != "objectNameChanged") { + memberModel.append(data); + } + } else { + memberModel.append(data); + } + } + + computeMembersWithValues(); + + if (isReloading) { + update.glyph = hifi.glyphs.playback_play + isReloading = false; + stopReload(); + } + + if (memberModel.count > 0) { + scrollSlider.y = 0; + list.contentY = 0; + } + } + + signal sendToScript(var message); +} \ No newline at end of file diff --git a/interface/resources/qml/controls-uit/Key.qml b/interface/resources/qml/controls-uit/Key.qml index b2c720368d..dd77fc92dc 100644 --- a/interface/resources/qml/controls-uit/Key.qml +++ b/interface/resources/qml/controls-uit/Key.qml @@ -45,18 +45,6 @@ Item { } } - onClicked: { - mouse.accepted = true; - Tablet.playSound(TabletEnums.ButtonClick); - - webEntity.synthesizeKeyPress(glyph); - webEntity.synthesizeKeyPress(glyph, mirrorText); - - if (toggle) { - toggled = !toggled; - } - } - onDoubleClicked: { mouse.accepted = true; } @@ -94,6 +82,14 @@ Item { onReleased: { if (containsMouse) { + Tablet.playSound(TabletEnums.ButtonClick); + + webEntity.synthesizeKeyPress(glyph); + webEntity.synthesizeKeyPress(glyph, mirrorText); + + if (toggle) { + toggled = !toggled; + } keyItem.state = "mouseOver"; } else { if (toggled) { diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index b9633104d5..7b1a177c86 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -70,7 +70,15 @@ ModalWindow { signal selectedFile(var file); signal canceled(); - + signal selected(int button); + function click(button) { + clickedButton = button; + selected(button); + destroy(); + } + + property int clickedButton: OriginalDialogs.StandardButton.NoButton; + Component.onCompleted: { console.log("Helper " + helper + " drives " + drives); @@ -628,7 +636,10 @@ ModalWindow { case Qt.Key_Backtab: event.accepted = false; break; - + case Qt.Key_Escape: + event.accepted = true; + root.click(OriginalDialogs.StandardButton.Cancel); + break; default: if (addToPrefix(event)) { event.accepted = true @@ -793,7 +804,11 @@ ModalWindow { case Qt.Key_Home: event.accepted = d.navigateHome(); break; - - } + + case Qt.Key_Escape: + event.accepted = true; + root.click(OriginalDialogs.StandardButton.Cancel); + break; + } } } diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 28c32c59de..c331532f5e 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -145,7 +145,7 @@ Rectangle { // Title text RalewayRegular { id: popText; - text: "PROOF OF PURCHASE"; + text: "Proof of Provenance"; // Text size size: 16; // Anchors @@ -155,7 +155,7 @@ Rectangle { anchors.right: titleBarText.right; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.darkGray; } // @@ -182,7 +182,7 @@ Rectangle { anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.baseGray; + color: hifi.colors.darkGray; } RalewaySemiBold { id: itemName; @@ -196,7 +196,7 @@ Rectangle { anchors.right: itemNameHeader.right; height: paintedHeight; // Style - color: hifi.colors.blueAccent; + color: hifi.colors.white; elide: Text.ElideRight; MouseArea { anchors.fill: parent; @@ -205,7 +205,7 @@ Rectangle { sendToScript({method: 'inspectionCertificate_showInMarketplaceClicked', marketplaceUrl: root.marketplaceUrl}); } onEntered: itemName.color = hifi.colors.blueHighlight; - onExited: itemName.color = hifi.colors.blueAccent; + onExited: itemName.color = hifi.colors.white; } } @@ -223,7 +223,7 @@ Rectangle { anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.lightGray; + color: hifi.colors.darkGray; } RalewayRegular { id: ownedBy; @@ -236,7 +236,7 @@ Rectangle { anchors.left: ownedByHeader.left; height: paintedHeight; // Style - color: hifi.colors.darkGray; + color: hifi.colors.white; elide: Text.ElideRight; } AnonymousProRegular { @@ -252,7 +252,7 @@ Rectangle { anchors.leftMargin: 6; anchors.right: ownedByHeader.right; // Style - color: hifi.colors.lightGray; + color: hifi.colors.white; elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter; } @@ -271,7 +271,7 @@ Rectangle { anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.lightGray; + color: hifi.colors.darkGray; } AnonymousProRegular { id: edition; @@ -285,7 +285,7 @@ Rectangle { anchors.right: editionHeader.right; height: paintedHeight; // Style - color: hifi.colors.darkGray; + color: hifi.colors.white; } RalewayRegular { @@ -302,7 +302,7 @@ Rectangle { anchors.rightMargin: 16; height: paintedHeight; // Style - color: hifi.colors.lightGray; + color: hifi.colors.darkGray; } AnonymousProRegular { id: dateOfPurchase; @@ -316,7 +316,7 @@ Rectangle { anchors.right: dateOfPurchaseHeader.right; height: paintedHeight; // Style - color: hifi.colors.darkGray; + color: hifi.colors.white; } RalewayRegular { @@ -349,7 +349,7 @@ Rectangle { // "Cancel" button HifiControlsUit.Button { - color: hifi.buttons.noneBorderless; + color: hifi.buttons.noneBorderlessWhite; colorScheme: hifi.colorSchemes.light; anchors.top: parent.top; anchors.left: parent.left; diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg b/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg index 9cecc79869..b39a55e4e8 100644 Binary files a/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg and b/interface/resources/qml/hifi/commerce/inspectionCertificate/images/cert-bg.jpg differ diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index de66be4a88..87b784bc4e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -36,6 +36,7 @@ Rectangle { property bool pendingInventoryReply: true; property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; + property int pendingItemCount: 0; // Style color: hifi.colors.white; Connections { @@ -79,18 +80,22 @@ Rectangle { onInventoryResult: { purchasesReceived = true; - if (root.pendingInventoryReply) { - inventoryTimer.start(); - } - if (result.status !== 'success') { console.log("Failed to get purchases", result.message); - } else { + } else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling var inventoryResult = processInventoryResult(result.data.assets); + var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex; purchasesModel.clear(); purchasesModel.append(inventoryResult); + root.pendingItemCount = 0; + for (var i = 0; i < purchasesModel.count; i++) { + if (purchasesModel.get(i).status === "pending") { + root.pendingItemCount++; + } + } + if (previousPurchasesModel.count !== 0) { checkIfAnyItemStatusChanged(); } else { @@ -103,6 +108,12 @@ Rectangle { previousPurchasesModel.append(inventoryResult); buildFilteredPurchasesModel(); + + purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning); + } + + if (root.pendingInventoryReply && root.pendingItemCount > 0) { + inventoryTimer.start(); } root.pendingInventoryReply = false; @@ -419,6 +430,8 @@ Rectangle { visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0); clip: true; model: filteredPurchasesModel; + snapMode: ListView.SnapToItem; + highlightRangeMode: ListView.StrictlyEnforceRange; // Anchors anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom; anchors.topMargin: 12; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 42ee44d584..780e08caf8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -25,8 +25,12 @@ Item { HifiConstants { id: hifi; } id: root; - property bool historyReceived: false; + property bool initialHistoryReceived: false; + property bool historyRequestPending: true; + property bool noMoreHistoryData: false; property int pendingCount: 0; + property int currentHistoryPage: 1; + property var pagesAlreadyAdded: new Array(); Connections { target: Commerce; @@ -36,32 +40,86 @@ Item { } onHistoryResult : { - historyReceived = true; - if (result.status === 'success') { - var sameItemCount = 0; - tempTransactionHistoryModel.clear(); - - tempTransactionHistoryModel.append(result.data.history); - - for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - if (!transactionHistoryModel.get(i)) { - sameItemCount = -1; - break; - } else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type && - tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) { - sameItemCount++; - } - } + root.initialHistoryReceived = true; + root.historyRequestPending = false; - if (sameItemCount !== tempTransactionHistoryModel.count) { - transactionHistoryModel.clear(); + if (result.status === 'success') { + var currentPage = parseInt(result.current_page); + + if (result.data.history.length === 0) { + root.noMoreHistoryData = true; + console.log("No more data to retrieve from Commerce.history() endpoint.") + } else if (root.currentHistoryPage === 1) { + var sameItemCount = 0; + tempTransactionHistoryModel.clear(); + + tempTransactionHistoryModel.append(result.data.history); + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - transactionHistoryModel.append(tempTransactionHistoryModel.get(i)); + if (!transactionHistoryModel.get(i)) { + sameItemCount = -1; + break; + } else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type && + tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) { + sameItemCount++; + } + } + + if (sameItemCount !== tempTransactionHistoryModel.count) { + transactionHistoryModel.clear(); + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { + transactionHistoryModel.append(tempTransactionHistoryModel.get(i)); + } + calculatePendingAndInvalidated(); + } + } else { + if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) { + console.log("Page " + currentPage + " of history has already been added to the list."); + } else { + // First, add the history result to a temporary model + tempTransactionHistoryModel.clear(); + tempTransactionHistoryModel.append(result.data.history); + + // Make a note that we've already added this page to the model... + root.pagesAlreadyAdded.push(currentPage); + + var insertionIndex = 0; + // If there's nothing in the model right now, we don't need to modify insertionIndex. + if (transactionHistoryModel.count !== 0) { + var currentIteratorPage; + // Search through the whole transactionHistoryModel and look for the insertion point. + // The insertion point is found when the result page from the server is less than + // the page that the current item came from, OR when we've reached the end of the whole model. + for (var i = 0; i < transactionHistoryModel.count; i++) { + currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage; + + if (currentPage < currentIteratorPage) { + insertionIndex = i; + break; + } else if (i === transactionHistoryModel.count - 1) { + insertionIndex = i + 1; + break; + } + } + } + + // Go through the results we just got back from the server, setting the "resultIsFromPage" + // property of those results and adding them to the main model. + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { + tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage); + transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i)) + } + + calculatePendingAndInvalidated(); } - calculatePendingAndInvalidated(); } } - refreshTimer.start(); + + // Only auto-refresh if the user hasn't scrolled + // and there is more data to grab + if (transactionHistory.atYBeginning && !root.noMoreHistoryData) { + refreshTimer.start(); + } } } @@ -134,9 +192,13 @@ Item { onVisibleChanged: { if (visible) { - historyReceived = false; + transactionHistoryModel.clear(); Commerce.balance(); - Commerce.history(); + initialHistoryReceived = false; + root.currentHistoryPage = 1; + root.noMoreHistoryData = false; + root.historyRequestPending = true; + Commerce.history(root.currentHistoryPage); } else { refreshTimer.stop(); } @@ -164,9 +226,12 @@ Item { id: refreshTimer; interval: 4000; onTriggered: { - console.log("Refreshing Wallet Home..."); - Commerce.balance(); - Commerce.history(); + if (transactionHistory.atYBeginning) { + console.log("Refreshing 1st Page of Recent Activity..."); + root.historyRequestPending = true; + Commerce.balance(); + Commerce.history(1); + } } } @@ -241,7 +306,7 @@ Item { anchors.right: parent.right; Item { - visible: transactionHistoryModel.count === 0 && root.historyReceived; + visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived; anchors.centerIn: parent; width: parent.width - 12; height: parent.height; @@ -364,7 +429,12 @@ Item { onAtYEndChanged: { if (transactionHistory.atYEnd) { console.log("User scrolled to the bottom of 'Recent Activity'."); - // Grab next page of results and append to model + if (!root.historyRequestPending && !root.noMoreHistoryData) { + // Grab next page of results and append to model + root.historyRequestPending = true; + Commerce.history(++root.currentHistoryPage); + console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity..."); + } } } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 217adc5083..f3cda533e9 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -62,7 +62,8 @@ StackView { var callback = rpcCalls[message.id]; if (!callback) { - console.log('No callback for message fromScript', JSON.stringify(message)); + // FIXME: We often recieve very long messages here, the logging of which is drastically slowing down the main thread + //console.log('No callback for message fromScript', JSON.stringify(message)); return; } delete rpcCalls[message.id]; diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index e934f18ab6..524bbf5f4d 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -127,9 +127,15 @@ Item { GridView { id: gridView + keyNavigationEnabled: false highlightFollowsCurrentItem: false + property int previousGridIndex: -1 + + // true if any of the buttons contains mouse + property bool containsMouse: false + anchors { fill: parent topMargin: 20 @@ -162,15 +168,31 @@ Item { flow: GridView.LeftToRight model: page.proxyModel - delegate: Item { + delegate: Control { id: wrapper width: gridView.cellWidth height: gridView.cellHeight + hoverEnabled: true + + property bool containsMouse: gridView.containsMouse + onHoveredChanged: { + if (hovered && !gridView.containsMouse) { + gridView.containsMouse = true + } else { + gridView.containsMouse = false + } + } + property var proxy: modelData TabletButton { id: tabletButton + + // Temporarily disable magnification + // scale: wrapper.hovered ? 1.25 : wrapper.containsMouse ? 0.75 : 1.0 + // Behavior on scale { NumberAnimation { duration: 200; easing.type: Easing.Linear } } + anchors.centerIn: parent gridView: wrapper.GridView.view buttonIndex: page.proxyModel.buttonIndex(uuid); @@ -224,6 +246,7 @@ Item { PageIndicator { id: pageIndicator currentIndex: swipeView.currentIndex + visible: swipeView.count > 1 delegate: Item { width: 15 diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index da544c2114..e7614e11b7 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -106,7 +106,7 @@ Item { if (isWebPage) { var webUrl = tabletApps.get(currentApp).appWebUrl; var scriptUrl = tabletApps.get(currentApp).scriptUrl; - loadSource("TabletWebView.qml"); + loadSource("hifi/tablet/TabletWebView.qml"); loadWebUrl(webUrl, scriptUrl); } else { loader.load(tabletApps.get(currentApp).appUrl); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f3c41565f8..f9a994124b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1851,6 +1851,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get()->setSetPrecisionPickingOperator([&](unsigned int rayPickID, bool value) { DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); + EntityTreeRenderer::setRenderDebugHullsOperator([] { + return Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls); + }); // Preload Tablet sounds DependencyManager::get()->preloadSounds(); @@ -2441,7 +2444,7 @@ void Application::initializeUi() { offscreenSurfaceCache->reserve(Web3DOverlay::QML, 2); } -void Application::updateCamera(RenderArgs& renderArgs) { +void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { PROFILE_RANGE(render, __FUNCTION__); PerformanceTimer perfTimer("updateCamera"); @@ -2456,6 +2459,7 @@ void Application::updateCamera(RenderArgs& renderArgs) { // Using the latter will cause the camera to wobble with idle animations, // or with changes from the face tracker if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { + _thirdPersonHMDCameraBoomValid= false; if (isHMDMode()) { mat4 camMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); _myCamera.setPosition(extractTranslation(camMat)); @@ -2468,12 +2472,25 @@ void Application::updateCamera(RenderArgs& renderArgs) { } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { if (isHMDMode()) { - auto hmdWorldMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix(); - _myCamera.setOrientation(glm::normalize(glmExtractRotation(hmdWorldMat))); - _myCamera.setPosition(extractTranslation(hmdWorldMat) + - myAvatar->getWorldOrientation() * boomOffset); + + if (!_thirdPersonHMDCameraBoomValid) { + const glm::vec3 CAMERA_OFFSET = glm::vec3(0.0f, 0.0f, 0.7f); + _thirdPersonHMDCameraBoom = cancelOutRollAndPitch(myAvatar->getHMDSensorOrientation()) * CAMERA_OFFSET; + _thirdPersonHMDCameraBoomValid = true; + } + + glm::mat4 thirdPersonCameraSensorToWorldMatrix = myAvatar->getSensorToWorldMatrix(); + + const glm::vec3 cameraPos = myAvatar->getHMDSensorPosition() + _thirdPersonHMDCameraBoom * myAvatar->getBoomLength(); + glm::mat4 sensorCameraMat = createMatFromQuatAndPos(myAvatar->getHMDSensorOrientation(), cameraPos); + glm::mat4 worldCameraMat = thirdPersonCameraSensorToWorldMatrix * sensorCameraMat; + + _myCamera.setOrientation(glm::normalize(glmExtractRotation(worldCameraMat))); + _myCamera.setPosition(extractTranslation(worldCameraMat)); } else { + _thirdPersonHMDCameraBoomValid = false; + _myCamera.setOrientation(myAvatar->getHead()->getOrientation()); if (Menu::getInstance()->isOptionChecked(MenuOption::CenterPlayerInView)) { _myCamera.setPosition(myAvatar->getDefaultEyePosition() @@ -2486,6 +2503,7 @@ void Application::updateCamera(RenderArgs& renderArgs) { } } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { + _thirdPersonHMDCameraBoomValid= false; if (isHMDMode()) { auto mirrorBodyOrientation = myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)); @@ -2520,6 +2538,7 @@ void Application::updateCamera(RenderArgs& renderArgs) { renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; } else if (_myCamera.getMode() == CAMERA_MODE_ENTITY) { + _thirdPersonHMDCameraBoomValid= false; EntityItemPointer cameraEntity = _myCamera.getCameraEntityPointer(); if (cameraEntity != nullptr) { if (isHMDMode()) { @@ -5004,8 +5023,6 @@ void Application::update(float deltaTime) { } } - PerformanceTimer perfTimer("misc"); - bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); @@ -5014,11 +5031,13 @@ void Application::update(float deltaTime) { // TODO: break these out into distinct perfTimers when they prove interesting { PROFILE_RANGE(app, "PickManager"); + PerformanceTimer perfTimer("pickManager"); DependencyManager::get()->update(); } { PROFILE_RANGE(app, "PointerManager"); + PerformanceTimer perfTimer("pointerManager"); DependencyManager::get()->update(); } @@ -5044,8 +5063,8 @@ void Application::update(float deltaTime) { // Update my voxel servers with my current voxel query... { PROFILE_RANGE_EX(app, "QueryOctree", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount()); - QMutexLocker viewLocker(&_viewMutex); PerformanceTimer perfTimer("queryOctree"); + QMutexLocker viewLocker(&_viewMutex); quint64 sinceLastQuery = now - _lastQueriedTime; const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; @@ -5081,12 +5100,14 @@ void Application::update(float deltaTime) { } } - avatarManager->postUpdate(deltaTime, getMain3DScene()); - + { + PerformanceTimer perfTimer("avatarManager/postUpdate"); + avatarManager->postUpdate(deltaTime, getMain3DScene()); + } { - PROFILE_RANGE_EX(app, "PreRenderLambdas", 0xffff0000, (uint64_t)0); - + PROFILE_RANGE_EX(app, "PostUpdateLambdas", 0xffff0000, (uint64_t)0); + PerformanceTimer perfTimer("postUpdateLambdas"); std::unique_lock guard(_postUpdateLambdasLock); for (auto& iter : _postUpdateLambdas) { iter.second(); @@ -5094,7 +5115,9 @@ void Application::update(float deltaTime) { _postUpdateLambdas.clear(); } - editRenderArgs([this](AppRenderArgs& appRenderArgs) { + + editRenderArgs([this, deltaTime](AppRenderArgs& appRenderArgs) { + PerformanceTimer perfTimer("editRenderArgs"); appRenderArgs._headPose= getHMDSensorPose(); auto myAvatar = getMyAvatar(); @@ -5140,7 +5163,7 @@ void Application::update(float deltaTime) { resizeGL(); } - this->updateCamera(appRenderArgs._renderArgs); + this->updateCamera(appRenderArgs._renderArgs, deltaTime); appRenderArgs._eyeToWorld = _myCamera.getTransform(); appRenderArgs._isStereo = false; @@ -5208,12 +5231,20 @@ void Application::update(float deltaTime) { } }); - AnimDebugDraw::getInstance().update(); + { + PerformanceTimer perfTimer("limitless"); + AnimDebugDraw::getInstance().update(); + } - DependencyManager::get()->update(); + { + PerformanceTimer perfTimer("limitless"); + DependencyManager::get()->update(); + } - // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over - getMain3DScene()->enqueueFrame(); + { // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over + PerformanceTimer perfTimer("enqueueFrame"); + getMain3DScene()->enqueueFrame(); + } } void Application::sendAvatarViewFrustum() { @@ -6793,7 +6824,8 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa void Application::takeSecondaryCameraSnapshot() { postLambdaEvent([this] { - Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot()); + QString snapshotPath = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot()); + emit DependencyManager::get()->stillSnapshotTaken(snapshotPath, true); }); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 479ce919a3..b01e998a21 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -146,7 +146,7 @@ public: void initializeGL(); void initializeUi(); - void updateCamera(RenderArgs& renderArgs); + void updateCamera(RenderArgs& renderArgs, float deltaTime); void paintGL(); void resizeGL(); @@ -695,6 +695,9 @@ private: void startHMDStandBySession(); void endHMDSession(); + glm::vec3 _thirdPersonHMDCameraBoom { 0.0f, 0.0f, -1.0f }; + bool _thirdPersonHMDCameraBoomValid { true }; + QUrl _avatarOverrideUrl; bool _saveAvatarOverrideUrl { false }; QObject* _renderEventHandler{ nullptr }; diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 9e6fabd439..087a93cffe 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -50,7 +50,7 @@ float LODManager::getLODIncreaseFPS() { const float LOD_ADJUST_RUNNING_AVG_TIMESCALE = 0.1f; // sec // // Assuming the measured value is affected by logic invoked by the runningAverage bumping up against its -// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage settle +// thresholds, we expect the adjustment to introduce a step-function. We want the runningAverage to settle // to the new value BEFORE we test it aginst its thresholds again. Hence we test on a period that is a few // multiples of the running average timescale: const uint64_t LOD_AUTO_ADJUST_PERIOD = 5 * (uint64_t)(LOD_ADJUST_RUNNING_AVG_TIMESCALE * (float)USECS_PER_MSEC); // usec diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d295e96867..3072ecf240 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -698,7 +698,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned, 0, false, drawStatusConfig, SLOT(setShowNetwork(bool))); } - addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls); + addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls, 0, false, qApp->getEntities().data(), SIGNAL(setRenderDebugHulls())); // Developer > Ask to Reset Settings addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AskToResetSettings, 0, false); @@ -758,6 +758,14 @@ Menu::Menu() { // Developer > Stats addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + // Developer > API Debugger + action = addActionToQMenuAndActionHash(developerMenu, "API Debugger"); + connect(action, &QAction::triggered, [] { + auto scriptEngines = DependencyManager::get(); + QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); + defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js"); + scriptEngines->loadScript(defaultScriptsLoc.toString()); + }); #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 68e417ba1d..e863a58e14 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2799,14 +2799,9 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { } bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { - auto cameraMode = qApp->getCamera().getMode(); - if (cameraMode == CAMERA_MODE_THIRD_PERSON) { - return false; - } else { - const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees - glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); - return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; - } + const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees + glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); + return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; } bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 50e0474831..9280899a1e 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -34,7 +34,6 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle } static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { - glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix()); // check for pinned hips. @@ -106,7 +105,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { if (avatarHeadPose.isValid()) { AnimPose pose(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation()); params.primaryControllerPoses[Rig::PrimaryControllerType_Head] = avatarToRigPose * pose; - params.primaryControllerActiveFlags[Rig::PrimaryControllerType_Head] = true; + params.primaryControllerFlags[Rig::PrimaryControllerType_Head] = (uint8_t)Rig::ControllerFlags::Enabled; } else { // even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and // down in desktop mode. @@ -114,7 +113,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // postMult 180 is necessary to convert head from -z forward to z forward. glm::quat headRot = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180; params.primaryControllerPoses[Rig::PrimaryControllerType_Head] = AnimPose(glm::vec3(1.0f), headRot, glm::vec3(0.0f)); - params.primaryControllerActiveFlags[Rig::PrimaryControllerType_Head] = false; + params.primaryControllerFlags[Rig::PrimaryControllerType_Head] = 0; } // @@ -135,10 +134,10 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { if (controllerPose.isValid()) { AnimPose pose(controllerPose.getRotation(), controllerPose.getTranslation()); params.primaryControllerPoses[pair.second] = avatarToRigPose * pose; - params.primaryControllerActiveFlags[pair.second] = true; + params.primaryControllerFlags[pair.second] = (uint8_t)Rig::ControllerFlags::Enabled; } else { params.primaryControllerPoses[pair.second] = AnimPose::identity; - params.primaryControllerActiveFlags[pair.second] = false; + params.primaryControllerFlags[pair.second] = 0; } } @@ -166,15 +165,15 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { if (controllerPose.isValid()) { AnimPose pose(controllerPose.getRotation(), controllerPose.getTranslation()); params.secondaryControllerPoses[pair.second] = avatarToRigPose * pose; - params.secondaryControllerActiveFlags[pair.second] = true; + params.secondaryControllerFlags[pair.second] = (uint8_t)Rig::ControllerFlags::Enabled; } else { params.secondaryControllerPoses[pair.second] = AnimPose::identity; - params.secondaryControllerActiveFlags[pair.second] = false; + params.secondaryControllerFlags[pair.second] = 0; } } // if hips are not under direct control, estimate the hips position. - if (avatarHeadPose.isValid() && !params.primaryControllerActiveFlags[Rig::PrimaryControllerType_Hips]) { + if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); if (!_prevHipsValid) { @@ -200,7 +199,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { AnimPose sensorToRigPose(invRigMat * myAvatar->getSensorToWorldMatrix()); params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * hips; - params.primaryControllerActiveFlags[Rig::PrimaryControllerType_Hips] = true; + params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated; } else { _prevHipsValid = false; @@ -336,17 +335,25 @@ void MySkeletonModel::updateFingers() { for (auto& link : chain) { int index = _rig.indexOfJoint(link.second); if (index >= 0) { + auto rotationFrameOffset = _jointRotationFrameOffsetMap.find(index); + if (rotationFrameOffset == _jointRotationFrameOffsetMap.end()) { + _jointRotationFrameOffsetMap.insert(std::pair(index, 0)); + rotationFrameOffset = _jointRotationFrameOffsetMap.find(index); + } auto pose = myAvatar->getControllerPoseInSensorFrame(link.first); + if (pose.valid) { glm::quat relRot = glm::inverse(prevAbsRot) * pose.getRotation(); // only set the rotation for the finger joints, not the hands. if (link.first != controller::Action::LEFT_HAND && link.first != controller::Action::RIGHT_HAND) { _rig.setJointRotation(index, true, relRot, CONTROLLER_PRIORITY); + rotationFrameOffset->second = 0; } prevAbsRot = pose.getRotation(); - } else { + } else if (rotationFrameOffset->second == 1) { // if the pose is invalid and was set on previous frame we do clear ( current frame offset = 1 ) _rig.clearJointAnimationPriority(index); } + rotationFrameOffset->second++; } } } diff --git a/interface/src/avatar/MySkeletonModel.h b/interface/src/avatar/MySkeletonModel.h index d9f57a439a..252b6c293b 100644 --- a/interface/src/avatar/MySkeletonModel.h +++ b/interface/src/avatar/MySkeletonModel.h @@ -28,6 +28,8 @@ private: AnimPose _prevHips; // sensor frame bool _prevHipsValid { false }; + + std::map _jointRotationFrameOffsetMap; }; #endif // hifi_MySkeletonModel_h diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index d7d36dabf6..51658ddef8 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -72,11 +72,16 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); } -void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { +void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& requestParams) { auto wallet = DependencyManager::get(); - QJsonObject request; - request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); - send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, request); + requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + + send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams); +} + +void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { + QJsonObject requestParams; + keysQuery(endpoint, success, fail, requestParams); } void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) { @@ -169,6 +174,7 @@ void Ledger::historySuccess(QNetworkReply& reply) { QJsonObject newDataData; newDataData["history"] = newHistoryArray; newData["data"] = newDataData; + newData["current_page"] = data["current_page"].toInt(); emit historyResult(newData); } @@ -176,8 +182,11 @@ void Ledger::historyFailure(QNetworkReply& reply) { failResponse("history", reply); } -void Ledger::history(const QStringList& keys) { - keysQuery("history", "historySuccess", "historyFailure"); +void Ledger::history(const QStringList& keys, const int& pageNumber) { + QJsonObject params; + params["per_page"] = 100; + params["page"] = pageNumber; + keysQuery("history", "historySuccess", "historyFailure", params); } // The api/failResponse is called just for the side effect of logging. diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 42eb0ffc49..5d90aa0808 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -29,7 +29,7 @@ public: bool receiveAt(const QString& hfc_key, const QString& old_key); void balance(const QStringList& keys); void inventory(const QStringList& keys); - void history(const QStringList& keys); + void history(const QStringList& keys, const int& pageNumber); void account(); void reset(); void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); @@ -79,6 +79,7 @@ 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, AccountManagerAuth::Type authType, QJsonObject request); + void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams); 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 62e87f9c66..320c7e041c 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -96,12 +96,12 @@ void QmlCommerce::inventory() { } } -void QmlCommerce::history() { +void QmlCommerce::history(const int& pageNumber) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->history(cachedPublicKeys); + ledger->history(cachedPublicKeys, pageNumber); } } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index c53e73d565..f2e6c82021 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -60,7 +60,7 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); - Q_INVOKABLE void history(); + Q_INVOKABLE void history(const int& pageNumber); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void reset(); Q_INVOKABLE void resetLocalWalletOnly(); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 4b355653b6..e36b84ac96 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -49,6 +49,8 @@ void CustomPromptResultFromScriptValue(const QScriptValue& object, CustomPromptR WindowScriptingInterface::WindowScriptingInterface() { const DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged); + connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &WindowScriptingInterface::disconnectedFromDomain); + connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); connect(qApp, &Application::svoImportRequested, [this](const QString& urlString) { @@ -134,6 +136,10 @@ void WindowScriptingInterface::promptAsync(const QString& message, const QString }); } +void WindowScriptingInterface::disconnectedFromDomain() { + emit domainChanged(""); +} + CustomPromptResult WindowScriptingInterface::customPrompt(const QVariant& config) { CustomPromptResult result; bool ok = false; @@ -176,10 +182,6 @@ bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) { return offscreenUi->isPointOnDesktopWindow(point); } -glm::vec2 WindowScriptingInterface::getDeviceSize() const { - return qApp->getDeviceSize(); -} - /// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and /// might be in same thread as a script that sets the reticle to invisible void WindowScriptingInterface::ensureReticleVisible() const { @@ -192,8 +194,7 @@ void WindowScriptingInterface::ensureReticleVisible() const { /// Display a "browse to directory" dialog. If `directory` is an invalid file or directory the browser will start at the current /// working directory. /// \param const QString& title title of the window -/// \param const QString& directory directory to start the file browser at -/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` +/// \param const QString& directory directory to start the directory browser at /// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QString& directory) { ensureReticleVisible(); @@ -214,8 +215,7 @@ QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QSt /// Display a "browse to directory" dialog. If `directory` is an invalid file or directory the browser will start at the current /// working directory. /// \param const QString& title title of the window -/// \param const QString& directory directory to start the file browser at -/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` +/// \param const QString& directory directory to start the directory browser at void WindowScriptingInterface::browseDirAsync(const QString& title, const QString& directory) { ensureReticleVisible(); QString path = directory; @@ -391,11 +391,15 @@ QString WindowScriptingInterface::checkVersion() { } int WindowScriptingInterface::getInnerWidth() { - return qApp->getWindow()->geometry().width(); + return qApp->getDeviceSize().x; } int WindowScriptingInterface::getInnerHeight() { - return qApp->getWindow()->geometry().height(); + return qApp->getDeviceSize().y; +} + +glm::vec2 WindowScriptingInterface::getDeviceSize() const { + return qApp->getDeviceSize(); } int WindowScriptingInterface::getX() { @@ -459,6 +463,41 @@ int WindowScriptingInterface::openMessageBox(QString title, QString text, int bu return createMessageBox(title, text, buttons, defaultButton); } +/**jsdoc + *

The buttons that may be included in a message box created by {@link Window.openMessageBox|openMessageBox} are defined by + * numeric values: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ButtonValueDescription
NoButton 0x0 An invalid button.
Ok 0x400 "OK"
Save 0x800 "Save"
SaveAll 0x1000 "Save All"
Open 0x2000 "Open"
Yes 0x4000 "Yes"
YesToAll 0x8000 "Yes to All"
No 0x10000 "No"
NoToAll 0x20000 "No to All"
Abort 0x40000 "Abort"
Retry 0x80000 "Retry"
Ignore 0x100000 "Ignore"
Close 0x200000 "Close"
Cancel 0x400000 "Cancel"
Discard 0x800000 "Discard" or "Don't Save"
Help 0x1000000 "Help"
Apply 0x2000000 "Apply"
Reset 0x4000000 "Reset"
RestoreDefaults 0x8000000 "Restore Defaults"
+ * @typedef Window.MessageBoxButton + */ int WindowScriptingInterface::createMessageBox(QString title, QString text, int buttons, int defaultButton) { auto messageBox = DependencyManager::get()->createMessageBox(OffscreenUi::ICON_INFORMATION, title, text, static_cast>(buttons), static_cast(defaultButton)); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index d223f95af4..af9c03a218 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -1,5 +1,5 @@ // -// WindowScriptingInterface.cpp +// WindowScriptingInterface.h // interface/src/scripting // // Created by Ryan Huffman on 4/29/14. @@ -33,12 +33,28 @@ QScriptValue CustomPromptResultToScriptValue(QScriptEngine* engine, const Custom void CustomPromptResultFromScriptValue(const QScriptValue& object, CustomPromptResult& result); +/**jsdoc + * The Window API provides various facilities not covered elsewhere: window dimensions, window focus, normal or entity camera + * view, clipboard, announcements, user connections, common dialog boxes, snapshots, file import, domain changes, domain + * physics. + * + * @namespace Window + * @property {number} innerWidth - The width of the drawable area of the Interface window (i.e., without borders or other + * chrome), in pixels. Read-only. + * @property {number} innerHeight - The height of the drawable area of the Interface window (i.e., without borders or other + * chrome) plus the height of the menu bar, in pixels. Read-only. + * @property {object} location - Provides facilities for working with your current metaverse location. See {@link location}. + * @property {number} x - The x coordinate of the top left corner of the Interface window on the display. Read-only. + * @property {number} y - The y coordinate of the top left corner of the Interface window on the display. Read-only. + */ + class WindowScriptingInterface : public QObject, public Dependency { Q_OBJECT Q_PROPERTY(int innerWidth READ getInnerWidth) Q_PROPERTY(int innerHeight READ getInnerHeight) Q_PROPERTY(int x READ getX) Q_PROPERTY(int y READ getY) + public: WindowScriptingInterface(); ~WindowScriptingInterface(); @@ -48,63 +64,623 @@ public: int getY(); public slots: + + /**jsdoc + * Check if the Interface window has focus. + * @function Window.hasFocus + * @returns {boolean} true if the Interface window has focus, otherwise false. + */ QScriptValue hasFocus(); + + /**jsdoc + * Make the Interface window have focus. + * @function Window.setFocus + */ void setFocus(); + + /**jsdoc + * Raise the Interface window if it is minimized, and give it focus. + * @function Window.raiseMainWindow + */ void raiseMainWindow(); + + /**jsdoc + * Display a dialog with the specified message and an "OK" button. The dialog is non-modal; the script continues without + * waiting for a user response. + * @function Window.alert + * @param {string} message="" - The message to display. + * @example Display a friendly greeting. + * Window.alert("Welcome!"); + * print("Script continues without waiting"); + */ void alert(const QString& message = ""); + + /**jsdoc + * Prompt the user to confirm something. Displays a modal dialog with a message plus "Yes" and "No" buttons. + * responds. + * @function Window.confirm + * @param {string} message="" - The question to display. + * @returns {boolean} true if the user selects "Yes", otherwise false. + * @example Ask the user a question requiring a yes/no answer. + * var answer = Window.confirm("Are you sure?"); + * print(answer); // true or false + */ QScriptValue confirm(const QString& message = ""); + + /**jsdoc + * Prompt the user to enter some text. Displays a modal dialog with a message and a text box, plus "OK" and "Cancel" + * buttons. + * @function Window.prompt + * @param {string} message - The question to display. + * @param {string} defaultText - The default answer text. + * @returns {string} The text that the user entered if they select "OK", otherwise "". + * @example Ask the user a question requiring a text answer. + * var answer = Window.prompt("Question", "answer"); + * if (answer === "") { + * print("User canceled"); + * } else { + * print("User answer: " + answer); + * } + */ QScriptValue prompt(const QString& message, const QString& defaultText); + + /**jsdoc + * Prompt the user to enter some text. Displays a non-modal dialog with a message and a text box, plus "OK" and "Cancel" + * buttons. A {@link Window.promptTextChanged|promptTextChanged} signal is emitted when the user OKs the dialog; no signal + * is emitted if the user cancels the dialog. + * @function Window.promptAsync + * @param {string} message - The question to display. + * @param {string} defaultText - The default answer text. + * @example Ask the user a question requiring a text answer without waiting for the answer. + * function onPromptTextChanged(text) { + * print("User answer: " + text); + * } + * Window.promptTextChanged.connect(onPromptTextChanged); + * + * Window.promptAsync("Question", "answer"); + * print("Script continues without waiting"); + */ void promptAsync(const QString& message = "", const QString& defaultText = ""); + + /**jsdoc + * Prompt the user for input in a custom, modal dialog. + * @deprecated This function is deprecated and will soon be removed. + * @function Window.customPrompt + * @param {object} config - Configures the modal dialog. + * @returns {object} The user's response. + */ CustomPromptResult customPrompt(const QVariant& config); + + /**jsdoc + * Prompt the user to choose a directory. Displays a modal dialog that navigates the directory tree. + * @function Window.browseDir + * @param {string} title="" - The title to display at the top of the dialog. + * @param {string} directory="" - The initial directory to start browsing at. + * @returns {string} The path of the directory if one is chosen, otherwise null. + * @example Ask the user to choose a directory. + * var directory = Window.browseDir("Select Directory", Paths.resources); + * print("Directory: " + directory); + */ QScriptValue browseDir(const QString& title = "", const QString& directory = ""); + + /**jsdoc + * Prompt the user to choose a directory. Displays a non-modal dialog that navigates the directory tree. A + * {@link Window.browseDirChanged|browseDirChanged} signal is emitted when a directory is chosen; no signal is emitted if + * the user cancels the dialog. + * @function Window.browseDirAsync + * @param {string} title="" - The title to display at the top of the dialog. + * @param {string} directory="" - The initial directory to start browsing at. + * @example Ask the user to choose a directory without waiting for the answer. + * function onBrowseDirChanged(directory) { + * print("Directory: " + directory); + * } + * Window.browseDirChanged.connect(onBrowseDirChanged); + * + * Window.browseDirAsync("Select Directory", Paths.resources); + * print("Script continues without waiting"); + */ void browseDirAsync(const QString& title = "", const QString& directory = ""); + + /**jsdoc + * Prompt the user to choose a file. Displays a modal dialog that navigates the directory tree. + * @function Window.browse + * @param {string} title="" - The title to display at the top of the dialog. + * @param {string} directory="" - The initial directory to start browsing at. + * @param {string} nameFilter="" - The types of files to display. Examples: "*.json" and + * "Images (*.png *.jpg *.svg)". All files are displayed if a filter isn't specified. + * @returns {string} The path and name of the file if one is chosen, otherwise null. + * @example Ask the user to choose an image file. + * var filename = Window.browse("Select Image File", Paths.resources, "Images (*.png *.jpg *.svg)"); + * print("File: " + filename); + */ QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + + /**jsdoc + * Prompt the user to choose a file. Displays a non-modal dialog that navigates the directory tree. A + * {@link Window.openFileChanged|openFileChanged} signal is emitted when a file is chosen; no signal is emitted if the user + * cancels the dialog. + * @function Window.browseAsync + * @param {string} title="" - The title to display at the top of the dialog. + * @param {string} directory="" - The initial directory to start browsing at. + * @param {string} nameFilter="" - The types of files to display. Examples: "*.json" and + * "Images (*.png *.jpg *.svg)". All files are displayed if a filter isn't specified. + * @example Ask the user to choose an image file without waiting for the answer. + * function onOpenFileChanged(filename) { + * print("File: " + filename); + * } + * Window.openFileChanged.connect(onOpenFileChanged); + * + * Window.browseAsync("Select Image File", Paths.resources, "Images (*.png *.jpg *.svg)"); + * print("Script continues without waiting"); + */ void browseAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + + /**jsdoc + * Prompt the user to specify the path and name of a file to save to. Displays a model dialog that navigates the directory + * tree and allows the user to type in a file name. + * @function Window.save + * @param {string} title="" - The title to display at the top of the dialog. + * @param {string} directory="" - The initial directory to start browsing at. + * @param {string} nameFilter="" - The types of files to display. Examples: "*.json" and + * "Images (*.png *.jpg *.svg)". All files are displayed if a filter isn't specified. + * @returns {string} The path and name of the file if one is specified, otherwise null. If a single file type + * is specified in the nameFilter, that file type extension is automatically appended to the result when appropriate. + * @example Ask the user to specify a file to save to. + * var filename = Window.save("Save to JSON file", Paths.resources, "*.json"); + * print("File: " + filename); + */ QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + + /**jsdoc + * Prompt the user to specify the path and name of a file to save to. Displays a non-model dialog that navigates the + * directory tree and allows the user to type in a file name. A {@link Window.saveFileChanged|saveFileChanged} signal is + * emitted when a file is specified; no signal is emitted if the user cancels the dialog. + * @function Window.saveAsync + * @param {string} title="" - The title to display at the top of the dialog. + * @param {string} directory="" - The initial directory to start browsing at. + * @param {string} nameFilter="" - The types of files to display. Examples: "*.json" and + * "Images (*.png *.jpg *.svg)". All files are displayed if a filter isn't specified. + * @example Ask the user to specify a file to save to without waiting for an answer. + * function onSaveFileChanged(filename) { + * print("File: " + filename); + * } + * Window.saveFileChanged.connect(onSaveFileChanged); + * + * Window.saveAsync("Save to JSON file", Paths.resources, "*.json"); + * print("Script continues without waiting"); + */ void saveAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + + /**jsdoc + * Prompt the user to choose an Asset Server item. Displays a modal dialog that navigates the tree of assets on the Asset + * Server. + * @function Window.browseAssets + * @param {string} title="" - The title to display at the top of the dialog. + * @param {string} directory="" - The initial directory to start browsing at. + * @param {string} nameFilter="" - The types of files to display. Examples: "*.json" and + * "Images (*.png *.jpg *.svg)". All files are displayed if a filter isn't specified. + * @returns {string} The path and name of the asset if one is chosen, otherwise null. + * @example Ask the user to select an FBX asset. + * var asset = Window.browseAssets("Select FBX File", "/", "*.fbx"); + * print("FBX file: " + asset); + */ QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + + /**jsdoc + * Prompt the user to choose an Asset Server item. Displays a non-modal dialog that navigates the tree of assets on the + * Asset Server. A {@link Window.assetsDirChanged|assetsDirChanged} signal is emitted when an asset is chosen; no signal is + * emitted if the user cancels the dialog. + * @function Window.browseAssetsAsync + * @param {string} title="" - The title to display at the top of the dialog. + * @param {string} directory="" - The initial directory to start browsing at. + * @param {string} nameFilter="" - The types of files to display. Examples: "*.json" and + * "Images (*.png *.jpg *.svg)". All files are displayed if a filter isn't specified. + * @example + * function onAssetsDirChanged(asset) { + * print("FBX file: " + asset); + * } + * Window.assetsDirChanged.connect(onAssetsDirChanged); + * + * Window.browseAssetsAsync("Select FBX File", "/", "*.fbx"); + * print("Script continues without waiting"); + */ void browseAssetsAsync(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); + + /**jsdoc + * Open the Asset Browser dialog. If a file to upload is specified, the user is prompted to enter the folder and name to + * map the file to on the asset server. + * @function Window.showAssetServer + * @param {string} uploadFile="" - The path and name of a file to upload to the asset server. + * @example Upload a file to the asset server. + * var filename = Window.browse("Select File to Add to Asset Server", Paths.resources); + * print("File: " + filename); + * Window.showAssetServer(filename); + */ void showAssetServer(const QString& upload = ""); + + /**jsdoc + * Get Interface's build number. + * @function Window.checkVersion + * @returns {string} - Interface's build number. + */ QString checkVersion(); + + /**jsdoc + * Copies text to the operating system's clipboard. + * @function Window.copyToClipboard + * @param {string} text - The text to copy to the operating system's clipboard. + */ void copyToClipboard(const QString& text); + + /**jsdoc + * Takes a snapshot of the current Interface view from the primary camera. When a still image only is captured, + * {@link Window.stillSnapshotTaken|stillSnapshotTaken} is emitted; when a still image plus moving images are captured, + * {@link Window.processingGifStarted|processingGifStarted} and {@link Window.processingGifCompleted|processingGifCompleted} + * are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings > + * General > Snapshots. + * @function Window.takeSnapshot + * @param {boolean} notify=true - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} + * signal. + * @param {boolean} includeAnimated=false - If true, a moving image is captured as an animated GIF in addition + * to a still image. + * @param {number} aspectRatio=0 - The width/height ratio of the snapshot required. If the value is 0 the + * full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the + * dimensions is adjusted in order to match the aspect ratio. + * @example Using the snapshot function and signals. + * function onStillSnapshottaken(path, notify) { + * print("Still snapshot taken: " + path); + * print("Notify: " + notify); + * } + * + * function onProcessingGifStarted(stillPath) { + * print("Still snapshot taken: " + stillPath); + * } + * + * function onProcessingGifCompleted(animatedPath) { + * print("Animated snapshot taken: " + animatedPath); + * } + * + * Window.stillSnapshotTaken.connect(onStillSnapshottaken); + * Window.processingGifStarted.connect(onProcessingGifStarted); + * Window.processingGifCompleted.connect(onProcessingGifCompleted); + * + * var notify = true; + * var animated = true; + * var aspect = 1920 / 1080; + * Window.takeSnapshot(notify, animated, aspect); + */ void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f); + + /**jsdoc + * Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API. + * @function Window.takeSecondaryCameraSnapshot + */ void takeSecondaryCameraSnapshot(); + + /**jsdoc + * Emit a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that + * indicates whether or not a user connection was successfully made using the Web API. + * @function Window.makeConnection + * @param {boolean} success - If true then {@link Window.connectionAdded|connectionAdded} is emitted, otherwise + * {@link Window.connectionError|connectionError} is emitted. + * @param {string} description - Descriptive text about the connection success or error. This is sent in the signal emitted. + */ void makeConnection(bool success, const QString& userNameOrError); + + /**jsdoc + * Display a notification message. Notifications are displayed in panels by the default script, nofications.js. An + * {@link Window.announcement|announcement} signal is emitted when this function is called. + * @function Window.displayAnnouncement + * @param {string} message - The announcement message. + * @example Send and capture an announcement message. + * function onAnnouncement(message) { + * // The message is also displayed as a notification by notifications.js. + * print("Announcement: " + message); + * } + * Window.announcement.connect(onAnnouncement); + * + * Window.displayAnnouncement("Hello"); + */ void displayAnnouncement(const QString& message); + + /**jsdoc + * Prepare a snapshot ready for sharing. A {@link Window.snapshotShared|snapshotShared} signal is emitted when the snapshot + * has been prepared. + * @function Window.shareSnapshot + * @param {string} path - The path and name of the image file to share. + * @param {string} href="" - The metaverse location where the snapshot was taken. + */ void shareSnapshot(const QString& path, const QUrl& href = QUrl("")); + + /**jsdoc + * Check to see if physics is active for you in the domain you're visiting - there is a delay between your arrival at a + * domain and physics becoming active for you in that domain. + * @function Window.isPhysicsEnabled + * @returns {boolean} true if physics is currently active for you, otherwise false. + * @example Wait for physics to be enabled when you change domains. + * function checkForPhysics() { + * var isPhysicsEnabled = Window.isPhysicsEnabled(); + * print("Physics enabled: " + isPhysicsEnabled); + * if (!isPhysicsEnabled) { + * Script.setTimeout(checkForPhysics, 1000); + * } + * } + * + * function onDomainChanged(domain) { + * print("Domain changed: " + domain); + * Script.setTimeout(checkForPhysics, 1000); + * } + * + * Window.domainChanged.connect(onDomainChanged); + */ bool isPhysicsEnabled(); + + /**jsdoc + * Set what to show on the PC display: normal view or entity camera view. The entity camera is configured using + * {@link Camera.setCameraEntity} and {@link Camera|Camera.mode}. + * @function Window.setDisplayTexture + * @param {Window.DisplayTexture} texture - The view to display. + * @returns {boolean} true if the display texture was successfully set, otherwise false. + */ + // See spectatorCamera.js for Valid parameter values. + /**jsdoc + *

The views that may be displayed on the PC display.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ValueView Displayed
""Normal view.
"resource://spectatorCameraFrame"Entity camera view.
+ * @typedef Window.DisplayTexture + */ bool setDisplayTexture(const QString& name); + + /**jsdoc + * Check if a 2D point is within the desktop window if in desktop mode, or the drawable area of the HUD overlay if in HMD + * mode. + * @function Window.isPointOnDesktopWindow + * @param {Vec2} point - The point to check. + * @returns {boolean} true if the point is within the window or HUD, otherwise false. + */ bool isPointOnDesktopWindow(QVariant point); + + /**jsdoc + * Get the size of the drawable area of the Interface window if in desktop mode or the HMD rendering surface if in HMD mode. + * @function Window.getDeviceSize + * @returns {Vec2} The width and height of the Interface window or HMD rendering surface, in pixels. + */ glm::vec2 getDeviceSize() const; + /**jsdoc + * Open a non-modal message box that can have a variety of button combinations. See also, + * {@link Window.updateMessageBox|updateMessageBox} and {@link Window.closeMessageBox|closeMessageBox}. + * @function Window.openMessageBox + * @param {string} title - The title to display for the message box. + * @param {string} text - Text to display in the message box. + * @param {Window.MessageBoxButton} buttons - The buttons to display on the message box; one or more button values added + * together. + * @param {Window.MessageBoxButton} defaultButton - The button that has focus when the message box is opened. + * @returns {number} The ID of the message box created. + * @example Ask the user whether that want to reset something. + * var messageBox; + * var resetButton = 0x4000000; + * var cancelButton = 0x400000; + * + * function onMessageBoxClosed(id, button) { + * if (id === messageBox) { + * if (button === resetButton) { + * print("Reset"); + * } else { + * print("Don't reset"); + * } + * } + * } + * Window.messageBoxClosed.connect(onMessageBoxClosed); + * + * messageBox = Window.openMessageBox("Reset Something", + * "Do you want to reset something?", + * resetButton + cancelButton, cancelButton); + */ int openMessageBox(QString title, QString text, int buttons, int defaultButton); + + /**jsdoc + * Update the content of a message box that was opened with {@link Window.openMessageBox|openMessageBox}. + * @function Window.updateMessageBox + * @param {number} id - The ID of the message box. + * @param {string} title - The title to display for the message box. + * @param {string} text - Text to display in the message box. + * @param {Window.MessageBoxButton} buttons - The buttons to display on the message box; one or more button values added + * together. + * @param {Window.MessageBoxButton} defaultButton - The button that has focus when the message box is opened. + */ void updateMessageBox(int id, QString title, QString text, int buttons, int defaultButton); + + /**jsdoc + * Close a message box that was opened with {@link Window.openMessageBox|openMessageBox}. + * @function Window.closeMessageBox + * @param {number} id - The ID of the message box. + */ void closeMessageBox(int id); private slots: void onMessageBoxSelected(int button); + void disconnectedFromDomain(); signals: - void domainChanged(const QString& domainHostname); + + /**jsdoc + * Triggered when you change the domain you're visiting. Warning: Is not emitted if you go to domain that + * isn't running. + * @function Window.domainChanged + * @param {string} domain - The domain's IP address. + * @returns {Signal} + * @example Report when you change domains. + * function onDomainChanged(domain) { + * print("Domain changed: " + domain); + * } + * + * Window.domainChanged.connect(onDomainChanged); + */ + void domainChanged(const QString& domain); + + /**jsdoc + * Triggered when you try to navigate to a *.json, *.svo, or *.svo.json URL in a Web browser within Interface. + * @function Window.svoImportRequested + * @param {string} url - The URL of the file to import. + * @returns {Signal} + */ void svoImportRequested(const QString& url); + + /**jsdoc + * Triggered when you try to visit a domain but are refused connection. + * @function Window.domainConnectionRefused + * @param {string} reasonMessage - A description of the refusal. + * @param {Window.ConnectionRefusedReason} reasonCode - Integer number that enumerates the reason for the refusal. + * @param {string} extraInfo - Extra information about the refusal. + * @returns {Signal} + */ void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo); + + /**jsdoc + * Triggered when a still snapshot has been taken by calling {@link Window.takeSnapshot|takeSnapshot} with + * includeAnimated = false. + * @function Window.stillSnapshotTaken + * @param {string} pathStillSnapshot - The path and name of the snapshot image file. + * @param {boolean} notify - The value of the notify parameter that {@link Window.takeSnapshot|takeSnapshot} + * was called with. + * @returns {Signal} + */ void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify); + + /**jsdoc + * Triggered when a snapshot submitted via {@link Window.shareSnapshot|shareSnapshot} is ready for sharing. The snapshot + * may then be shared via the {@link Account.metaverseServerURL} Web API. + * @function Window.snapshotShared + * @param {boolean} isError - true if an error was encountered preparing the snapshot for sharing, otherwise + * false. + * @param {string} reply - JSON-formatted information about the snapshot. + * @returns {Signal} + */ void snapshotShared(bool isError, const QString& reply); + + /**jsdoc + * Triggered when the snapshot images have been captured by {@link Window.takeSnapshot|takeSnapshot} and the GIF is + * starting to be processed. + * @function Window.processingGifStarted + * @param {string} pathStillSnapshot - The path and name of the still snapshot image file. + * @returns {Signal} + */ void processingGifStarted(const QString& pathStillSnapshot); + + /**jsdoc + * Triggered when a GIF has been prepared of the snapshot images captured by {@link Window.takeSnapshot|takeSnapshot}. + * @function Window.processingGifCompleted + * @param {string} pathAnimatedSnapshot - The path and name of the moving snapshot GIF file. + * @returns {Signal} + */ void processingGifCompleted(const QString& pathAnimatedSnapshot); + + /**jsdoc + * Triggered when you've successfully made a user connection. + * @function Window.connectionAdded + * @param {string} message - A description of the success. + * @returns {Signal} + */ void connectionAdded(const QString& connectionName); + + /**jsdoc + * Triggered when you failed to make a user connection. + * @function Window.connectionError + * @param {string} message - A description of the error. + * @returns {Signal} + */ void connectionError(const QString& errorString); + + /**jsdoc + * Triggered when a message is announced by {@link Window.displayAnnouncement|displayAnnouncement}. + * @function Window.announcement + * @param {string} message - The message text. + * @returns {Signal} + */ void announcement(const QString& message); + + /**jsdoc + * Triggered when the user closes a message box that was opened with {@link Window.openMessageBox|openMessageBox}. + * @function Window.messageBoxClosed + * @param {number} id - The ID of the message box that was closed. + * @param {number} button - The button that the user clicked. If the user presses Esc, the Cancel button value is returned, + * whether or not the Cancel button is displayed in the message box. + * @returns {Signal} + */ void messageBoxClosed(int id, int button); + + /**jsdoc + * Triggered when the user chooses a directory in a {@link Window.browseDirAsync|browseDirAsync} dialog. + * @function Window.browseDirChanged + * @param {string} directory - The directory the user chose in the dialog. + * @returns {Signal} + */ void browseDirChanged(QString browseDir); + + /**jsdoc + * Triggered when the user chooses an asset in a {@link Window.browseAssetsAsync|browseAssetsAsync} dialog. + * @function Window.assetsDirChanged + * @param {string} asset - The path and name of the asset the user chose in the dialog. + * @returns {Signal} + */ void assetsDirChanged(QString assetsDir); + + /**jsdoc + * Triggered when the user specifies a file in a {@link Window.saveAsync|saveAsync} dialog. + * @function Window.saveFileChanged + * @param {string} filename - The path and name of the file that the user specified in the dialog. + * @returns {Signal} + */ void saveFileChanged(QString filename); + + /**jsdoc + * Triggered when the user chooses a file in a {@link Window.browseAsync|browseAsync} dialog. + * @function Window.openFileChanged + * @param {string} filename - The path and name of the file the user chose in the dialog. + * @returns {Signal} + */ void openFileChanged(QString filename); + + /**jsdoc + * Triggered when the user OKs a {@link Window.promptAsync|promptAsync} dialog. + * @function Window.promptTextChanged + * @param {string} text - The text the user entered in the dialog. + * @returns {Signal} + */ void promptTextChanged(QString text); - // triggered when window size or position changes + + /**jsdoc + * Triggered when the position or size of the Interface window changes. + * @function Window.geometryChanged + * @param {Rect} geometry - The position and size of the drawable area of the Interface window. + * @returns {Signal} + * @example Report the position of size of the Interface window when it changes. + * function onWindowGeometryChanged(rect) { + * print("Window geometry: " + JSON.stringify(rect)); + * } + * + * Window.geometryChanged.connect(onWindowGeometryChanged); + */ void geometryChanged(QRect geometry); private: diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 84a6fe1da4..23e09fe5ca 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -210,8 +210,8 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index ff267adc1f..a7c586df1f 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -397,10 +397,8 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index e34e68e053..c3a772e565 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -165,10 +165,8 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index e06fa22692..679a4afbd2 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -143,10 +143,8 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 454945d31c..ca7af3a08b 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -219,10 +219,8 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 62675bacea..a8425d482f 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -286,10 +286,8 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 3bd4f84cd6..ec586771af 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -317,10 +317,8 @@ vectorType ModelOverlay::mapJoints(mapFunction function) const { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index abd657074c..04c0d01fa2 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -233,6 +233,7 @@ public slots: /**jsdoc * Get the overlay script object. * @function Overlays.getOverlayObject + * @deprecated This function is deprecated and will soon be removed. * @param {Uuid} overlayID - The ID of the overlay to get the script object of. * @returns {object} The script object for the overlay if found. */ diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index 425b7e8155..c535b7e503 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -138,10 +138,8 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index 92431d7716..1ae0dc6fd3 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -139,10 +139,8 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 8cb7f7757b..137d273c7d 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -58,10 +58,8 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index a66b57c365..f036516f1e 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -235,10 +235,8 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index a5da5e99b6..96b68f5ae0 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -222,10 +222,6 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - // mark the TabletProxy object as cpp ownership. - QObject* tablet = tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"); - _webSurface->getSurfaceContext()->engine()->setObjectOwnership(tablet, QQmlEngine::CppOwnership); // Override min fps for tablet UI, for silky smooth scrolling setMaxFPS(90); } @@ -497,10 +493,8 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. - * @property {boolean} isSolid=false - Synonyms: solid, isFilled, - * filled, and filed. Antonyms: isWire and wire. - * Deprecated: The erroneous property spelling "filed" is deprecated and support for it will - * be removed. + * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled. + * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 34a3207f47..daa1c2618f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1265,7 +1265,8 @@ glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJoin return position; } -void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, +void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, + bool leftArmEnabled, bool rightArmEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) { @@ -1279,7 +1280,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab glm::vec3 handPosition = leftHandPose.trans(); glm::quat handRotation = leftHandPose.rot(); - if (!hipsEnabled) { + if (!hipsEnabled || hipsEstimated) { // prevent the hand IK targets from intersecting the torso handPosition = deflectHandFromTorso(handPosition, hipsShapeInfo, spineShapeInfo, spine1ShapeInfo, spine2ShapeInfo); } @@ -1326,7 +1327,7 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab glm::vec3 handPosition = rightHandPose.trans(); glm::quat handRotation = rightHandPose.rot(); - if (!hipsEnabled) { + if (!hipsEnabled || hipsEstimated) { // prevent the hand IK targets from intersecting the torso handPosition = deflectHandFromTorso(handPosition, hipsShapeInfo, spineShapeInfo, spine1ShapeInfo, spine2ShapeInfo); } @@ -1550,20 +1551,20 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo _animVars.set("isTalking", params.isTalking); _animVars.set("notIsTalking", !params.isTalking); - bool headEnabled = params.primaryControllerActiveFlags[PrimaryControllerType_Head]; - bool leftHandEnabled = params.primaryControllerActiveFlags[PrimaryControllerType_LeftHand]; - bool rightHandEnabled = params.primaryControllerActiveFlags[PrimaryControllerType_RightHand]; - bool hipsEnabled = params.primaryControllerActiveFlags[PrimaryControllerType_Hips]; - bool leftFootEnabled = params.primaryControllerActiveFlags[PrimaryControllerType_LeftFoot]; - bool rightFootEnabled = params.primaryControllerActiveFlags[PrimaryControllerType_RightFoot]; - bool spine2Enabled = params.primaryControllerActiveFlags[PrimaryControllerType_Spine2]; - - bool leftArmEnabled = params.secondaryControllerActiveFlags[SecondaryControllerType_LeftArm]; - bool rightArmEnabled = params.secondaryControllerActiveFlags[SecondaryControllerType_RightArm]; + bool headEnabled = params.primaryControllerFlags[PrimaryControllerType_Head] & (uint8_t)ControllerFlags::Enabled; + bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled; + bool rightHandEnabled = params.primaryControllerFlags[PrimaryControllerType_RightHand] & (uint8_t)ControllerFlags::Enabled; + bool hipsEnabled = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; + bool hipsEstimated = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated; + bool leftFootEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftFoot] & (uint8_t)ControllerFlags::Enabled; + bool rightFootEnabled = params.primaryControllerFlags[PrimaryControllerType_RightFoot] & (uint8_t)ControllerFlags::Enabled; + bool spine2Enabled = params.primaryControllerFlags[PrimaryControllerType_Spine2] & (uint8_t)ControllerFlags::Enabled; + bool leftArmEnabled = params.secondaryControllerFlags[SecondaryControllerType_LeftArm] & (uint8_t)ControllerFlags::Enabled; + bool rightArmEnabled = params.secondaryControllerFlags[SecondaryControllerType_RightArm] & (uint8_t)ControllerFlags::Enabled; updateHead(headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]); - updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, leftArmEnabled, rightArmEnabled, dt, + updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, dt, params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo); @@ -1623,7 +1624,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo for (int i = 0; i < (int)NumSecondaryControllerTypes; i++) { int index = indexOfJoint(secondaryControllerJointNames[i]); if (index >= 0) { - if (params.secondaryControllerActiveFlags[i]) { + if (params.secondaryControllerFlags[i] & (uint8_t)ControllerFlags::Enabled) { ikNode->setSecondaryTargetInRigFrame(index, params.secondaryControllerPoses[i]); } else { ikNode->clearSecondaryTarget(index); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 7d7b55f4a8..6968d7c3af 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -69,11 +69,16 @@ public: NumSecondaryControllerTypes }; + enum class ControllerFlags : uint8_t { + Enabled = 0x01, + Estimated = 0x02 + }; + struct ControllerParameters { AnimPose primaryControllerPoses[NumPrimaryControllerTypes]; // rig space - bool primaryControllerActiveFlags[NumPrimaryControllerTypes]; + uint8_t primaryControllerFlags[NumPrimaryControllerTypes]; AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space - bool secondaryControllerActiveFlags[NumSecondaryControllerTypes]; + uint8_t secondaryControllerFlags[NumSecondaryControllerTypes]; bool isTalking; FBXJointShapeInfo hipsShapeInfo; FBXJointShapeInfo spineShapeInfo; @@ -252,7 +257,8 @@ protected: void buildAbsoluteRigPoses(const AnimPoseVec& relativePoses, AnimPoseVec& absolutePosesOut); void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix); - void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, + void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool hipsEstimated, + bool leftArmEnabled, bool rightArmEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo); diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 04b865fa03..a13ef07cd4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -37,6 +37,16 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) : SkeletonModel::~SkeletonModel() { } +void SkeletonModel::setURL(const QUrl& url) { + _texturesLoaded = false; + Model::setURL(url); +} + +void SkeletonModel::setTextures(const QVariantMap& textures) { + _texturesLoaded = false; + Model::setTextures(textures); +} + void SkeletonModel::initJointStates() { const FBXGeometry& geometry = getFBXGeometry(); glm::mat4 modelOffset = glm::scale(_scale) * glm::translate(_offset); @@ -142,6 +152,13 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { Parent::simulate(deltaTime, fullUpdate); } + // FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem and ModelOverlay, + // but Avatars don't get updates in the same way + if (!_texturesLoaded && getGeometry() && getGeometry()->areTexturesLoaded()) { + _texturesLoaded = true; + updateRenderItems(); + } + if (!isActive() || !_owningAvatar->isMyAvatar()) { return; // only simulate for own avatar } diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index 919e82825c..f911ad0c5a 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -31,6 +31,9 @@ public: SkeletonModel(Avatar* owningAvatar, QObject* parent = nullptr); ~SkeletonModel(); + Q_INVOKABLE void setURL(const QUrl& url) override; + Q_INVOKABLE void setTextures(const QVariantMap& textures) override; + void initJointStates() override; void simulate(float deltaTime, bool fullUpdate = true) override; @@ -115,8 +118,6 @@ protected: void computeBoundingShape(); -protected: - bool getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; Avatar* _owningAvatar; @@ -128,6 +129,9 @@ protected: glm::vec3 _defaultEyeModelPosition; float _headClipDistance; // Near clip distance to use if no separate head model + +private: + bool _texturesLoaded { false }; }; #endif // hifi_SkeletonModel_h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 3a737e581b..c2a201a965 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -42,6 +42,7 @@ size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; +std::function EntityTreeRenderer::_renderDebugHullsOperator = [] { return false; }; EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, AbstractScriptingServicesInterface* scriptingServices) : diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index dccd14aac6..f5cedfdd01 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -116,10 +116,14 @@ public: EntityItemPointer getEntity(const EntityItemID& id); void onEntityChanged(const EntityItemID& id); + static void setRenderDebugHullsOperator(std::function renderDebugHullsOperator) { _renderDebugHullsOperator = renderDebugHullsOperator; } + static bool shouldRenderDebugHulls() { return _renderDebugHullsOperator(); } + signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); + void setRenderDebugHulls(); public slots: void addingEntity(const EntityItemID& entityID); @@ -255,6 +259,8 @@ private: static int _entitiesScriptEngineCount; static CalculateEntityLoadingPriority _calculateEntityLoadingPriorityFunc; static std::function _entitiesShouldFadeFunction; + + static std::function _renderDebugHullsOperator; }; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 9e32977de1..fb9aba636b 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -141,6 +141,10 @@ std::shared_ptr make_renderer(const EntityItemPointer& entity) { } EntityRenderer::EntityRenderer(const EntityItemPointer& entity) : _entity(entity) { + connect(entity.get(), &EntityItem::requestRenderUpdate, this, [&] { + _needsRenderUpdate = true; + emit requestRenderUpdate(); + }); } EntityRenderer::~EntityRenderer() { } @@ -311,6 +315,9 @@ void EntityRenderer::setSubRenderItemIDs(const render::ItemIDs& ids) { // Returns true if the item needs to have updateInscene called because of internal rendering // changes (animation, fading, etc) bool EntityRenderer::needsRenderUpdate() const { + if (_needsRenderUpdate) { + return true; + } if (_prevIsTransparent != isTransparent()) { return true; } @@ -331,10 +338,6 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity return true; } - if (_visible != entity->getVisible()) { - return true; - } - if (_moving != entity->isMovingRelativeToParent()) { return true; } @@ -363,6 +366,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); + _needsRenderUpdate = false; }); } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 5deb69711d..8eb82e2c6e 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -125,6 +125,7 @@ protected: bool _prevIsTransparent { false }; bool _visible { false }; bool _moving { false }; + bool _needsRenderUpdate { false }; // Only touched on the rendering thread bool _renderUpdateQueued{ false }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c756070bc3..86237e75a4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -954,8 +954,23 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { using namespace render; using namespace render::entities; +ModelEntityRenderer::ModelEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { + connect(DependencyManager::get().data(), &EntityTreeRenderer::setRenderDebugHulls, this, [&] { + _needsCollisionGeometryUpdate = true; + emit requestRenderUpdate(); + }); +} + +void ModelEntityRenderer::setKey(bool didVisualGeometryRequestSucceed) { + if (didVisualGeometryRequestSucceed) { + _itemKey = ItemKey::Builder().withTypeMeta(); + } else { + _itemKey = ItemKey::Builder().withTypeMeta().withTypeShape(); + } +} + ItemKey ModelEntityRenderer::getKey() { - return ItemKey::Builder().withTypeMeta().withTypeShape(); + return _itemKey; } uint32_t ModelEntityRenderer::metaFetchMetaSubItems(ItemIDs& subItems) { @@ -1080,7 +1095,7 @@ bool ModelEntityRenderer::needsRenderUpdate() const { return true; } - if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { + if (!_texturesLoaded) { return true; } @@ -1209,7 +1224,10 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // Check for addition if (_hasModel && !(bool)_model) { model = std::make_shared(nullptr, entity.get()); - connect(model.get(), &Model::setURLFinished, this, &ModelEntityRenderer::requestRenderUpdate); + connect(model.get(), &Model::setURLFinished, this, [&](bool didVisualGeometryRequestSucceed) { + setKey(didVisualGeometryRequestSucceed); + emit requestRenderUpdate(); + }); connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); connect(entity.get(), &RenderableModelEntityItem::requestCollisionGeometryUpdate, this, &ModelEntityRenderer::flagForCollisionGeometryUpdate); model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity)); @@ -1275,7 +1293,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce setCollisionMeshKey(entity->getCollisionMeshKey()); _needsCollisionGeometryUpdate = false; ShapeType type = entity->getShapeType(); - if (_showCollisionGeometry && type != SHAPE_TYPE_STATIC_MESH && type != SHAPE_TYPE_NONE) { + if (DependencyManager::get()->shouldRenderDebugHulls() && type != SHAPE_TYPE_STATIC_MESH && type != SHAPE_TYPE_NONE) { // NOTE: it is OK if _collisionMeshKey is nullptr model::MeshPointer mesh = collisionMeshCache.getMesh(_collisionMeshKey); // NOTE: the model will render the collisionGeometry if it has one @@ -1310,6 +1328,8 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { _texturesLoaded = true; model->updateRenderItems(); + } else if (!_texturesLoaded) { + emit requestRenderUpdate(); } // When the individual mesh parts of a model finish fading, they will mark their Model as needing updating @@ -1342,20 +1362,11 @@ void ModelEntityRenderer::doRender(RenderArgs* args) { DETAILED_PROFILE_RANGE(render_detail, "MetaModelRender"); DETAILED_PERFORMANCE_TIMER("RMEIrender"); - ModelPointer model; - withReadLock([&]{ - model = _model; - }); - - // If we don't have a model, or the model doesn't have visual geometry, render our bounding box as green wireframe - if (!model || (model && model->didVisualGeometryRequestFail())) { - static glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); - gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getModelTransform()); // we want to include the scale as well - DependencyManager::get()->renderWireCubeInstance(args, batch, greenColor); - return; - } - + // If the model doesn't have visual geometry, render our bounding box as green wireframe + static glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); + gpu::Batch& batch = *args->_batch; + batch.setModelTransform(getModelTransform()); // we want to include the scale as well + DependencyManager::get()->renderWireCubeInstance(args, batch, greenColor); // Enqueue updates for the next frame #if WANT_EXTRA_DEBUGGING @@ -1366,12 +1377,6 @@ void ModelEntityRenderer::doRender(RenderArgs* args) { // Remap textures for the next frame to avoid flicker // remapTextures(); - - bool showCollisionGeometry = (bool)(args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS); - if (showCollisionGeometry != _showCollisionGeometry) { - _showCollisionGeometry = showCollisionGeometry; - flagForCollisionGeometryUpdate(); - } } void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const QStringList& modelJointNames) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index b7a339e1e1..1f3c4af054 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -133,12 +133,13 @@ class ModelEntityRenderer : public TypedEntityRenderer _texturedPipeline; +// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 11 to avoid collisions +static int32_t PARTICLE_UNIFORM_SLOT { 11 }; static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, const ShapeKey& key) { auto texturedPipeline = _texturedPipeline.lock(); @@ -38,6 +40,9 @@ static ShapePipelinePointer shapePipelineFactory(const ShapePlumber& plumber, co auto fragShader = gpu::Shader::createPixel(std::string(textured_particle_frag)); auto program = gpu::Shader::createProgram(vertShader, fragShader); + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("particleBuffer"), PARTICLE_UNIFORM_SLOT)); + gpu::Shader::makeProgram(*program, slotBindings); _texturedPipeline = texturedPipeline = gpu::Pipeline::create(program, state); } @@ -320,7 +325,7 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { transform.setScale(vec3(1)); } batch.setModelTransform(transform); - batch.setUniformBuffer(0, _uniformBuffer); + batch.setUniformBuffer(PARTICLE_UNIFORM_SLOT, _uniformBuffer); batch.setInputFormat(_vertexFormat); batch.setInputBuffer(0, _particleBuffer, 0, sizeof(GpuParticle)); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 21764dff7f..b362721cde 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -33,8 +33,9 @@ using namespace render; using namespace render::entities; static uint8_t CUSTOM_PIPELINE_NUMBER { 0 }; -static const int32_t PAINTSTROKE_TEXTURE_SLOT{ 0 }; -static const int32_t PAINTSTROKE_UNIFORM_SLOT{ 0 }; +static const int32_t PAINTSTROKE_TEXTURE_SLOT { 0 }; +// FIXME: This is interfering with the uniform buffers in DeferredLightingEffect.cpp, so use 11 to avoid collisions +static const int32_t PAINTSTROKE_UNIFORM_SLOT { 11 }; static gpu::Stream::FormatPointer polylineFormat; static gpu::PipelinePointer polylinePipeline; #ifdef POLYLINE_ENTITY_USE_FADE_EFFECT diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 496f8b7323..ad0202457e 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1422,27 +1422,29 @@ bool RenderablePolyVoxEntityItem::getMeshes(MeshProxyList& result) { } bool success = false; - MeshProxy* meshProxy = nullptr; - glm::mat4 transform = voxelToLocalMatrix(); - withReadLock([&] { - gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); - if (!_meshReady) { - // we aren't ready to return a mesh. the caller will have to try again later. - success = false; - } else if (numVertices == 0) { - // we are ready, but there are no triangles in the mesh. - success = true; - } else { - success = true; - // the mesh will be in voxel-space. transform it into object-space - meshProxy = new SimpleMeshProxy( - _mesh->map([=](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, - [=](glm::vec3 color){ return color; }, - [=](glm::vec3 normal){ return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, - [&](uint32_t index){ return index; })); - result << meshProxy; - } - }); + if (_mesh) { + MeshProxy* meshProxy = nullptr; + glm::mat4 transform = voxelToLocalMatrix(); + withReadLock([&] { + gpu::BufferView::Index numVertices = (gpu::BufferView::Index)_mesh->getNumVertices(); + if (!_meshReady) { + // we aren't ready to return a mesh. the caller will have to try again later. + success = false; + } else if (numVertices == 0) { + // we are ready, but there are no triangles in the mesh. + success = true; + } else { + success = true; + // the mesh will be in voxel-space. transform it into object-space + meshProxy = new SimpleMeshProxy( + _mesh->map([=](glm::vec3 position) { return glm::vec3(transform * glm::vec4(position, 1.0f)); }, + [=](glm::vec3 color) { return color; }, + [=](glm::vec3 normal) { return glm::normalize(glm::vec3(transform * glm::vec4(normal, 0.0f))); }, + [&](uint32_t index) { return index; })); + result << meshProxy; + } + }); + } return success; } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 591bccaabe..3ecce5ec38 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -145,10 +145,8 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { updateSkyboxMap(); if (_needBackgroundUpdate) { - if (BackgroundStage::isIndexInvalid(_backgroundIndex)) { + if (_skyboxMode == COMPONENT_MODE_ENABLED && BackgroundStage::isIndexInvalid(_backgroundIndex)) { _backgroundIndex = _backgroundStage->addBackground(_background); - } else { - } _needBackgroundUpdate = false; } @@ -164,20 +162,27 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { } if (_visible) { - // FInally, push the light visible in the frame - // THe directional key light for sure - _stage->_currentFrame.pushSunLight(_sunIndex); - - // The ambient light only if it has a valid texture to render with - if (_validAmbientTexture || _validSkyboxTexture) { - _stage->_currentFrame.pushAmbientLight(_ambientIndex); + // Finally, push the light visible in the frame + if (_keyLightMode == COMPONENT_MODE_DISABLED) { + _stage->_currentFrame.pushSunLight(_stage->getSunOffLight()); + } else if (_keyLightMode == COMPONENT_MODE_ENABLED) { + _stage->_currentFrame.pushSunLight(_sunIndex); } // The background only if the mode is not inherit - if (_backgroundMode != BACKGROUND_MODE_INHERIT) { + if (_skyboxMode == COMPONENT_MODE_DISABLED) { + _backgroundStage->_currentFrame.pushBackground(INVALID_INDEX); + } else if (_skyboxMode == COMPONENT_MODE_ENABLED) { _backgroundStage->_currentFrame.pushBackground(_backgroundIndex); } + // The ambient light only if it has a valid texture to render with + if (_ambientLightMode == COMPONENT_MODE_DISABLED) { + _stage->_currentFrame.pushAmbientLight(_stage->getAmbientOffLight()); + } else if (_ambientLightMode == COMPONENT_MODE_ENABLED) { + _stage->_currentFrame.pushAmbientLight(_ambientIndex); + } + // Haze only if the mode is not inherit if (_hazeMode != COMPONENT_MODE_INHERIT) { _hazeStage->_currentFrame.pushHaze(_hazeIndex); @@ -194,14 +199,13 @@ void ZoneEntityRenderer::removeFromScene(const ScenePointer& scene, Transaction& Parent::removeFromScene(scene, transaction); } - void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { DependencyManager::get()->updateZone(entity->getID()); // FIXME one of the bools here could become true between being fetched and being reset, // resulting in a lost update - bool sunChanged = entity->keyLightPropertiesChanged(); - bool backgroundChanged = entity->backgroundPropertiesChanged(); + bool keyLightChanged = entity->keyLightPropertiesChanged(); + bool ambientLightChanged = entity->ambientLightPropertiesChanged(); bool skyboxChanged = entity->skyboxPropertiesChanged(); bool hazeChanged = entity->hazePropertiesChanged(); @@ -211,9 +215,9 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen _lastDimensions = entity->getScaledDimensions(); _keyLightProperties = entity->getKeyLightProperties(); + _ambientLightProperties = entity->getAmbientLightProperties(); _skyboxProperties = entity->getSkyboxProperties(); _hazeProperties = entity->getHazeProperties(); - _stageProperties = entity->getStageProperties(); #if 0 if (_lastShapeURL != _typedEntity->getCompoundShapeURL()) { @@ -236,15 +240,15 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen updateKeyZoneItemFromEntity(); - if (sunChanged) { - updateKeySunFromEntity(); + if (keyLightChanged) { + updateKeySunFromEntity(entity); } - if (sunChanged || skyboxChanged) { - updateKeyAmbientFromEntity(); + if (ambientLightChanged) { + updateAmbientLightFromEntity(entity); } - if (backgroundChanged || skyboxChanged) { + if (skyboxChanged) { updateKeyBackgroundFromEntity(entity); } @@ -267,9 +271,10 @@ ItemKey ZoneEntityRenderer::getKey() { bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (entity->keyLightPropertiesChanged() || - entity->backgroundPropertiesChanged() || + entity->ambientLightPropertiesChanged() || entity->hazePropertiesChanged() || entity->skyboxPropertiesChanged()) { + return true; } @@ -310,7 +315,9 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return false; } -void ZoneEntityRenderer::updateKeySunFromEntity() { +void ZoneEntityRenderer::updateKeySunFromEntity(const TypedEntityPointer& entity) { + setKeyLightMode((ComponentMode)entity->getKeyLightMode()); + const auto& sunLight = editSunLight(); sunLight->setType(model::Light::SUN); sunLight->setPosition(_lastPosition); @@ -322,20 +329,22 @@ void ZoneEntityRenderer::updateKeySunFromEntity() { sunLight->setDirection(_keyLightProperties.getDirection()); } -void ZoneEntityRenderer::updateKeyAmbientFromEntity() { +void ZoneEntityRenderer::updateAmbientLightFromEntity(const TypedEntityPointer& entity) { + setAmbientLightMode((ComponentMode)entity->getAmbientLightMode()); + const auto& ambientLight = editAmbientLight(); ambientLight->setType(model::Light::AMBIENT); ambientLight->setPosition(_lastPosition); ambientLight->setOrientation(_lastRotation); - // Set the keylight - ambientLight->setAmbientIntensity(_keyLightProperties.getAmbientIntensity()); + // Set the ambient light + ambientLight->setAmbientIntensity(_ambientLightProperties.getAmbientIntensity()); - if (_keyLightProperties.getAmbientURL().isEmpty()) { + if (_ambientLightProperties.getAmbientURL().isEmpty()) { setAmbientURL(_skyboxProperties.getURL()); } else { - setAmbientURL(_keyLightProperties.getAmbientURL()); + setAmbientURL(_ambientLightProperties.getAmbientURL()); } } @@ -370,8 +379,9 @@ void ZoneEntityRenderer::updateHazeFromEntity(const TypedEntityPointer& entity) } void ZoneEntityRenderer::updateKeyBackgroundFromEntity(const TypedEntityPointer& entity) { + setSkyboxMode((ComponentMode)entity->getSkyboxMode()); + editBackground(); - setBackgroundMode(entity->getBackgroundMode()); setSkyboxColor(_skyboxProperties.getColorVec3()); setProceduralUserData(entity->getUserData()); setSkyboxURL(_skyboxProperties.getURL()); @@ -403,7 +413,6 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) { _ambientTextureURL = ambientUrl; if (_ambientTextureURL.isEmpty()) { - _validAmbientTexture = false; _pendingAmbientTexture = false; _ambientTexture.clear(); @@ -431,7 +440,6 @@ void ZoneEntityRenderer::updateAmbientMap() { _ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY); } editAmbientLight()->setAmbientMap(texture); - _validAmbientTexture = true; } else { qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << _ambientTexture->getURL(); } @@ -447,7 +455,6 @@ void ZoneEntityRenderer::setSkyboxURL(const QString& skyboxUrl) { _skyboxTextureURL = skyboxUrl; if (_skyboxTextureURL.isEmpty()) { - _validSkyboxTexture = false; _pendingSkyboxTexture = false; _skyboxTexture.clear(); @@ -467,7 +474,6 @@ void ZoneEntityRenderer::updateSkyboxMap() { auto texture = _skyboxTexture->getGPUTexture(); if (texture) { editSkybox()->setCubemap(texture); - _validSkyboxTexture = true; } else { qCDebug(entitiesrenderer) << "Failed to load Skybox texture:" << _skyboxTexture->getURL(); } @@ -475,14 +481,22 @@ void ZoneEntityRenderer::updateSkyboxMap() { } } -void ZoneEntityRenderer::setBackgroundMode(BackgroundMode mode) { - _backgroundMode = mode; -} - void ZoneEntityRenderer::setHazeMode(ComponentMode mode) { _hazeMode = mode; } +void ZoneEntityRenderer::setKeyLightMode(ComponentMode mode) { + _keyLightMode = mode; +} + +void ZoneEntityRenderer::setAmbientLightMode(ComponentMode mode) { + _ambientLightMode = mode; +} + +void ZoneEntityRenderer::setSkyboxMode(ComponentMode mode) { + _skyboxMode = mode; +} + void ZoneEntityRenderer::setSkyboxColor(const glm::vec3& color) { editSkybox()->setColor(color); } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 050a8a4386..3174f00f4f 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -46,16 +46,20 @@ protected: private: void updateKeyZoneItemFromEntity(); - void updateKeySunFromEntity(); - void updateKeyAmbientFromEntity(); + void updateKeySunFromEntity(const TypedEntityPointer& entity); + void updateAmbientLightFromEntity(const TypedEntityPointer& entity); void updateHazeFromEntity(const TypedEntityPointer& entity); void updateKeyBackgroundFromEntity(const TypedEntityPointer& entity); void updateAmbientMap(); void updateSkyboxMap(); void setAmbientURL(const QString& ambientUrl); void setSkyboxURL(const QString& skyboxUrl); - void setBackgroundMode(BackgroundMode mode); + void setHazeMode(ComponentMode mode); + void setKeyLightMode(ComponentMode mode); + void setAmbientLightMode(ComponentMode mode); + void setSkyboxMode(ComponentMode mode); + void setSkyboxColor(const glm::vec3& color); void setProceduralUserData(const QString& userData); @@ -84,8 +88,10 @@ private: const model::SunSkyStagePointer _background{ std::make_shared() }; const model::HazePointer _haze{ std::make_shared() }; - BackgroundMode _backgroundMode{ BACKGROUND_MODE_INHERIT }; - ComponentMode _hazeMode{ COMPONENT_MODE_INHERIT }; + ComponentMode _keyLightMode { COMPONENT_MODE_INHERIT }; + ComponentMode _ambientLightMode { COMPONENT_MODE_INHERIT }; + ComponentMode _skyboxMode { COMPONENT_MODE_INHERIT }; + ComponentMode _hazeMode { COMPONENT_MODE_INHERIT }; indexed_container::Index _sunIndex{ LightStage::INVALID_INDEX }; indexed_container::Index _shadowIndex{ LightStage::INVALID_INDEX }; @@ -104,20 +110,18 @@ private: bool _needHazeUpdate{ true }; KeyLightPropertyGroup _keyLightProperties; + AmbientLightPropertyGroup _ambientLightProperties; HazePropertyGroup _hazeProperties; - StagePropertyGroup _stageProperties; SkyboxPropertyGroup _skyboxProperties; // More attributes used for rendering: QString _ambientTextureURL; NetworkTexturePointer _ambientTexture; bool _pendingAmbientTexture{ false }; - bool _validAmbientTexture{ false }; QString _skyboxTextureURL; NetworkTexturePointer _skyboxTexture; bool _pendingSkyboxTexture{ false }; - bool _validSkyboxTexture{ false }; QString _proceduralUserData; Transform _renderTransform; diff --git a/libraries/entities/src/AmbientLightPropertyGroup.cpp b/libraries/entities/src/AmbientLightPropertyGroup.cpp new file mode 100644 index 0000000000..dbcb0eef75 --- /dev/null +++ b/libraries/entities/src/AmbientLightPropertyGroup.cpp @@ -0,0 +1,155 @@ +// +// AmbientLightPropertyGroup.cpp +// libraries/entities/src +// +// Created by Nissim Hadar on 2017/12/24. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AmbientLightPropertyGroup.h" + +#include +#include + +#include "EntityItemProperties.h" +#include "EntityItemPropertiesMacros.h" + +const float AmbientLightPropertyGroup::DEFAULT_AMBIENT_LIGHT_INTENSITY = 0.5f; + +void AmbientLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, + QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { + + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_AMBIENT_LIGHT_INTENSITY, AmbientLight, ambientLight, AmbientIntensity, ambientIntensity); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_AMBIENT_LIGHT_URL, AmbientLight, ambientLight, AmbientURL, ambientURL); +} + +void AmbientLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ambientLight, ambientIntensity, float, setAmbientIntensity); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(ambientLight, ambientURL, QString, setAmbientURL); + + // legacy property support + COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(ambientLightAmbientIntensity, float, setAmbientIntensity, getAmbientIntensity); +} + +void AmbientLightPropertyGroup::merge(const AmbientLightPropertyGroup& other) { + COPY_PROPERTY_IF_CHANGED(ambientIntensity); + COPY_PROPERTY_IF_CHANGED(ambientURL); +} + +void AmbientLightPropertyGroup::debugDump() const { + qCDebug(entities) << " AmbientLightPropertyGroup: ---------------------------------------------"; + qCDebug(entities) << " ambientIntensity:" << getAmbientIntensity(); + qCDebug(entities) << " ambientURL:" << getAmbientURL(); +} + +void AmbientLightPropertyGroup::listChangedProperties(QList& out) { + if (ambientIntensityChanged()) { + out << "ambientLight-ambientIntensity"; + } + if (ambientURLChanged()) { + out << "ambientLight-ambientURL"; + } +} + +bool AmbientLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, getAmbientIntensity()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_URL, getAmbientURL()); + + return true; +} + +bool AmbientLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, + int& processedBytes) { + + int bytesRead = 0; + bool overwriteLocalData = true; + bool somethingChanged = false; + + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, float, setAmbientIntensity); + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_URL, QString, setAmbientURL); + + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_AMBIENT_LIGHT_INTENSITY, AmbientIntensity); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_AMBIENT_LIGHT_URL, AmbientURL); + + processedBytes += bytesRead; + + Q_UNUSED(somethingChanged); + + return true; +} + +void AmbientLightPropertyGroup::markAllChanged() { + _ambientIntensityChanged = true; + _ambientURLChanged = true; +} + +EntityPropertyFlags AmbientLightPropertyGroup::getChangedProperties() const { + EntityPropertyFlags changedProperties; + + CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_INTENSITY, ambientIntensity); + CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_URL, ambientURL); + + return changedProperties; +} + +void AmbientLightPropertyGroup::getProperties(EntityItemProperties& properties) const { + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(AmbientLight, AmbientIntensity, getAmbientIntensity); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(AmbientLight, AmbientURL, getAmbientURL); +} + +bool AmbientLightPropertyGroup::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(AmbientLight, AmbientIntensity, ambientIntensity, setAmbientIntensity); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(AmbientLight, AmbientURL, ambientURL, setAmbientURL); + + return somethingChanged; +} + +EntityPropertyFlags AmbientLightPropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties; + + requestedProperties += PROP_AMBIENT_LIGHT_INTENSITY; + requestedProperties += PROP_AMBIENT_LIGHT_URL; + + return requestedProperties; +} + +void AmbientLightPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, getAmbientIntensity()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_URL, getAmbientURL()); +} + +int AmbientLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, float, setAmbientIntensity); + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_URL, QString, setAmbientURL); + + return bytesRead; +} diff --git a/libraries/entities/src/StagePropertyGroup.h b/libraries/entities/src/AmbientLightPropertyGroup.h similarity index 71% rename from libraries/entities/src/StagePropertyGroup.h rename to libraries/entities/src/AmbientLightPropertyGroup.h index 3e5bad5443..fbbc7c9900 100644 --- a/libraries/entities/src/StagePropertyGroup.h +++ b/libraries/entities/src/AmbientLightPropertyGroup.h @@ -1,21 +1,25 @@ // -// StagePropertyGroup.h +// AmbientLightPropertyGroup.h // libraries/entities/src // -// Created by Brad Hefta-Gaub on 12/4/13. +// Created by Nissim Hadar on 2017/12/24. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_StagePropertyGroup_h -#define hifi_StagePropertyGroup_h + +#ifndef hifi_AmbientLightPropertyGroup_h +#define hifi_AmbientLightPropertyGroup_h + +#include + +#include #include - -#include "PropertyGroup.h" #include "EntityItemPropertiesMacros.h" +#include "PropertyGroup.h" class EntityItemProperties; class EncodeBitstreamParams; @@ -23,11 +27,7 @@ class OctreePacketData; class EntityTreeElementExtraEncodeData; class ReadBitstreamToTreeParams; -#include -#include - - -class StagePropertyGroup : public PropertyGroup { +class AmbientLightPropertyGroup : public PropertyGroup { public: // EntityItemProperty related helpers virtual void copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, @@ -35,7 +35,7 @@ public: EntityItemProperties& defaultEntityProperties) const override; virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) override; - void merge(const StagePropertyGroup& other); + void merge(const AmbientLightPropertyGroup& other); virtual void debugDump() const override; virtual void listChangedProperties(QList& out) override; @@ -74,23 +74,10 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - static const bool DEFAULT_STAGE_SUN_MODEL_ENABLED; - static const float DEFAULT_STAGE_LATITUDE; - static const float DEFAULT_STAGE_LONGITUDE; - static const float DEFAULT_STAGE_ALTITUDE; - static const quint16 DEFAULT_STAGE_DAY; - static const float DEFAULT_STAGE_HOUR; + static const float DEFAULT_AMBIENT_LIGHT_INTENSITY; - float calculateHour() const; - uint16_t calculateDay() const; - - DEFINE_PROPERTY(PROP_STAGE_SUN_MODEL_ENABLED, SunModelEnabled, sunModelEnabled, bool, DEFAULT_STAGE_SUN_MODEL_ENABLED); - DEFINE_PROPERTY(PROP_STAGE_LATITUDE, Latitude, latitude, float, DEFAULT_STAGE_LATITUDE); - DEFINE_PROPERTY(PROP_STAGE_LONGITUDE, Longitude, longitude, float, DEFAULT_STAGE_LONGITUDE); - DEFINE_PROPERTY(PROP_STAGE_ALTITUDE, Altitude, altitude, float, DEFAULT_STAGE_ALTITUDE); - DEFINE_PROPERTY(PROP_STAGE_DAY, Day, day, uint16_t, DEFAULT_STAGE_DAY); - DEFINE_PROPERTY(PROP_STAGE_HOUR, Hour, hour, float, DEFAULT_STAGE_HOUR); - DEFINE_PROPERTY(PROP_STAGE_AUTOMATIC_HOURDAY, AutomaticHourDay, automaticHourDay, bool, false); + DEFINE_PROPERTY(PROP_AMBIENT_LIGHT_INTENSITY, AmbientIntensity, ambientIntensity, float, DEFAULT_AMBIENT_LIGHT_INTENSITY); + DEFINE_PROPERTY_REF(PROP_AMBIENT_LIGHT_URL, AmbientURL, ambientURL, QString, ""); }; -#endif // hifi_StagePropertyGroup_h +#endif // hifi_AmbientLightPropertyGroup_h diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 91e26d0a3c..722ad416e4 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -965,7 +965,10 @@ void EntityItem::setMass(float mass) { void EntityItem::setHref(QString value) { auto href = value.toLower(); - if (! (value.toLower().startsWith("hifi://")) ) { + + // If the string has something and doesn't start with with "hifi://" it shouldn't be set + // We allow the string to be empty, because that's the initial state of this property + if ( !(value.toLower().startsWith("hifi://")) && !value.isEmpty()) { return; } withWriteLock([&] { @@ -2712,9 +2715,17 @@ bool EntityItem::getVisible() const { } void EntityItem::setVisible(bool value) { + bool changed = false; withWriteLock([&] { - _visible = value; + if (_visible != value) { + changed = true; + _visible = value; + } }); + + if (changed) { + emit requestRenderUpdate(); + } } bool EntityItem::isChildOfMyAvatar() const { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index d8fb3b3ab6..ecfb7b5dcd 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -469,6 +469,9 @@ public: static QString _marketplacePublicKey; static void retrieveMarketplacePublicKey(); +signals: + void requestRenderUpdate(); + protected: QHash _changeHandlers; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 111b851962..8bfce7e2d2 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -33,8 +33,8 @@ AnimationPropertyGroup EntityItemProperties::_staticAnimation; SkyboxPropertyGroup EntityItemProperties::_staticSkybox; HazePropertyGroup EntityItemProperties::_staticHaze; -StagePropertyGroup EntityItemProperties::_staticStage; KeyLightPropertyGroup EntityItemProperties::_staticKeyLight; +AmbientLightPropertyGroup EntityItemProperties::_staticAmbientLight; EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - 1); @@ -79,6 +79,7 @@ void EntityItemProperties::debugDump() const { getSkybox().debugDump(); getHaze().debugDump(); getKeyLight().debugDump(); + getAmbientLight().debugDump(); qCDebug(entities) << " changed properties..."; EntityPropertyFlags props = getChangedProperties(); @@ -182,47 +183,7 @@ void EntityItemProperties::setShapeTypeFromString(const QString& shapeName) { } } -using BackgroundPair = std::pair; -const std::array BACKGROUND_MODES = { { - BackgroundPair { BACKGROUND_MODE_INHERIT, { "inherit" } }, - BackgroundPair { BACKGROUND_MODE_SKYBOX, { "skybox" } } -} }; - -QString EntityItemProperties::getBackgroundModeAsString() const { - return BACKGROUND_MODES[_backgroundMode].second; -} - -QString EntityItemProperties::getBackgroundModeString(BackgroundMode mode) { - return BACKGROUND_MODES[mode].second; -} - -void EntityItemProperties::setBackgroundModeFromString(const QString& backgroundMode) { - auto result = std::find_if(BACKGROUND_MODES.begin(), BACKGROUND_MODES.end(), [&](const BackgroundPair& pair) { - return (pair.second == backgroundMode); - }); - if (result != BACKGROUND_MODES.end()) { - _backgroundMode = result->first; - _backgroundModeChanged = true; - } -} - -using ComponentPair = std::pair; -const std::array COMPONENT_MODES = { { - ComponentPair{ COMPONENT_MODE_INHERIT,{ "inherit" } }, - ComponentPair{ COMPONENT_MODE_DISABLED,{ "disabled" } }, - ComponentPair{ COMPONENT_MODE_ENABLED,{ "enabled" } } -} }; - -QString EntityItemProperties::getHazeModeAsString() const { - // return "inherit" if _hazeMode is not valid - if (_hazeMode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[_hazeMode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_INHERIT].second; - } -} - -QString EntityItemProperties::getHazeModeString(uint32_t mode) { +QString EntityItemProperties::getComponentModeAsString(uint32_t mode) { // return "inherit" if mode is not valid if (mode < COMPONENT_MODE_ITEM_COUNT) { return COMPONENT_MODES[mode].second; @@ -231,10 +192,27 @@ QString EntityItemProperties::getHazeModeString(uint32_t mode) { } } -void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { - auto result = std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { - return (pair.second == hazeMode); +QString EntityItemProperties::getHazeModeAsString() const { + return getComponentModeAsString(_hazeMode); +} + +QString EntityItemProperties::getComponentModeString(uint32_t mode) { + // return "inherit" if mode is not valid + if (mode < COMPONENT_MODE_ITEM_COUNT) { + return COMPONENT_MODES[mode].second; + } else { + return COMPONENT_MODES[COMPONENT_MODE_INHERIT].second; + } +} + +std::array::const_iterator EntityItemProperties::findComponent(const QString& mode) { + return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { + return (pair.second == mode); }); +} + +void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { + auto result = findComponent(hazeMode); if (result != COMPONENT_MODES.end()) { _hazeMode = result->first; @@ -242,6 +220,45 @@ void EntityItemProperties::setHazeModeFromString(const QString& hazeMode) { } } +QString EntityItemProperties::getKeyLightModeAsString() const { + return getComponentModeAsString(_keyLightMode); +} + +void EntityItemProperties::setKeyLightModeFromString(const QString& keyLightMode) { + auto result = findComponent(keyLightMode); + + if (result != COMPONENT_MODES.end()) { + _keyLightMode = result->first; + _keyLightModeChanged = true; + } +} + +QString EntityItemProperties::getAmbientLightModeAsString() const { + return getComponentModeAsString(_ambientLightMode); +} + +void EntityItemProperties::setAmbientLightModeFromString(const QString& ambientLightMode) { + auto result = findComponent(ambientLightMode); + + if (result != COMPONENT_MODES.end()) { + _ambientLightMode = result->first; + _ambientLightModeChanged = true; + } +} + +QString EntityItemProperties::getSkyboxModeAsString() const { + return getComponentModeAsString(_skyboxMode); +} + +void EntityItemProperties::setSkyboxModeFromString(const QString& skyboxMode) { + auto result = findComponent(skyboxMode); + + if (result != COMPONENT_MODES.end()) { + _skyboxMode = result->first; + _skyboxModeChanged = true; + } +} + EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; @@ -327,9 +344,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); CHECK_PROPERTY_CHANGE(PROP_NAME, name); - CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode); CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); + CHECK_PROPERTY_CHANGE(PROP_KEY_LIGHT_MODE, keyLightMode); + CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); + CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); @@ -379,8 +398,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); + changedProperties += _ambientLight.getChangedProperties(); changedProperties += _skybox.getChangedProperties(); - changedProperties += _stage.getChangedProperties(); changedProperties += _haze.getChangedProperties(); return changedProperties; @@ -553,10 +572,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Zones only if (_type == EntityTypes::Zone) { _keyLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + _ambientLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BACKGROUND_MODE, backgroundMode, getBackgroundModeAsString()); - - _stage.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed); @@ -565,6 +582,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString()); _haze.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_KEY_LIGHT_MODE, keyLightMode, getKeyLightModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AMBIENT_LIGHT_MODE, ambientLightMode, getAmbientLightModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); } // Web only @@ -751,9 +772,10 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName); COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionSoundURL, QString, setCollisionSoundURL); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(backgroundMode, BackgroundMode); - COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(hazeMode, HazeMode); + COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(keyLightMode, KeyLightMode); + COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(ambientLightMode, AmbientLightMode); + COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(skyboxMode, SkyboxMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); @@ -783,8 +805,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool _animation.copyFromScriptValue(object, _defaultSettings); _keyLight.copyFromScriptValue(object, _defaultSettings); + _ambientLight.copyFromScriptValue(object, _defaultSettings); _skybox.copyFromScriptValue(object, _defaultSettings); - _stage.copyFromScriptValue(object, _defaultSettings); _haze.copyFromScriptValue(object, _defaultSettings); COPY_PROPERTY_FROM_QSCRIPTVALUE(xTextureURL, QString, setXTextureURL); @@ -910,9 +932,10 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(name); COPY_PROPERTY_IF_CHANGED(collisionSoundURL); - COPY_PROPERTY_IF_CHANGED(backgroundMode); - COPY_PROPERTY_IF_CHANGED(hazeMode); + COPY_PROPERTY_IF_CHANGED(keyLightMode); + COPY_PROPERTY_IF_CHANGED(ambientLightMode); + COPY_PROPERTY_IF_CHANGED(skyboxMode); COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); @@ -932,8 +955,8 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { _animation.merge(other._animation); _keyLight.merge(other._keyLight); + _ambientLight.merge(other._ambientLight); _skybox.merge(other._skybox); - _stage.merge(other._stage); _haze.merge(other._haze); COPY_PROPERTY_IF_CHANGED(xTextureURL); @@ -1101,13 +1124,11 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); - ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLightAmbientIntensity, keyLightAmbientIntensity, float); ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_DIRECTION, KeyLightDirection, keyLightDirection, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray); ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); - ADD_PROPERTY_TO_MAP(PROP_BACKGROUND_MODE, BackgroundMode, backgroundMode, BackgroundMode); ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float); ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); @@ -1158,14 +1179,6 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_COLOR, Skybox, skybox, Color, color); ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_URL, Skybox, skybox, URL, url); - ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_SUN_MODEL_ENABLED, Stage, stage, SunModelEnabled, sunModelEnabled); - ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_LATITUDE, Stage, stage, Latitude, latitude); - ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_LONGITUDE, Stage, stage, Longitude, longitude); - ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_ALTITUDE, Stage, stage, Altitude, altitude); - ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_DAY, Stage, stage, Day, day); - ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_HOUR, Stage, stage, Hour, hour); - ADD_GROUP_PROPERTY_TO_MAP(PROP_STAGE_AUTOMATIC_HOURDAY, Stage, stage, AutomaticHourDay, automaticHourDay); - ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool); ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool); ADD_PROPERTY_TO_MAP(PROP_FILTER_URL, FilterURL, filterURL, QString); @@ -1188,6 +1201,10 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_RANGE, Haze, haze, HazeKeyLightRange, hazeKeyLightRange); ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_ALTITUDE, Haze, haze, HazeKeyLightAltitude, hazeKeyLightAltitude); + ADD_PROPERTY_TO_MAP(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t); // FIXME - these are not yet handled @@ -1414,14 +1431,12 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy _staticKeyLight.setProperties(properties); _staticKeyLight.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - _staticStage.setProperties(properties); - _staticStage.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + _staticAmbientLight.setProperties(properties); + _staticAmbientLight.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)properties.getShapeType()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL()); - APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, (uint32_t)properties.getBackgroundMode()); - _staticSkybox.setProperties(properties); _staticSkybox.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -1432,6 +1447,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode()); _staticHaze.setProperties(properties); _staticHaze.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + + APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)properties.getKeyLightMode()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)properties.getAmbientLightMode()); + APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1768,12 +1787,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int } if (properties.getType() == EntityTypes::Zone) { - properties.getKeyLight().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); - properties.getStage().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); + properties.getKeyLight().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + properties.getAmbientLight().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); @@ -1782,7 +1800,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode); properties.getHaze().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - } + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); + } if (properties.getType() == EntityTypes::PolyVox) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VOXEL_VOLUME_SIZE, glm::vec3, setVoxelVolumeSize); @@ -2010,13 +2032,13 @@ void EntityItemProperties::markAllChanged() { _staticCertificateVersionChanged = true; _keyLight.markAllChanged(); + _ambientLight.markAllChanged(); + _skybox.markAllChanged(); - _backgroundModeChanged = true; _hazeModeChanged = true; _animation.markAllChanged(); _skybox.markAllChanged(); - _stage.markAllChanged(); _haze.markAllChanged(); _sourceUrlChanged = true; @@ -2354,14 +2376,22 @@ QList EntityItemProperties::listChangedProperties() { out += "staticCertificateVersion"; } - if (backgroundModeChanged()) { - out += "backgroundMode"; - } - if (hazeModeChanged()) { out += "hazeMode"; } + if (keyLightModeChanged()) { + out += "keyLightMode"; + } + + if (ambientLightModeChanged()) { + out += "ambientLightMode"; + } + + if (skyboxModeChanged()) { + out += "skyboxMode"; + } + if (voxelVolumeSizeChanged()) { out += "voxelVolumeSize"; } @@ -2463,8 +2493,8 @@ QList EntityItemProperties::listChangedProperties() { getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); + getAmbientLight().listChangedProperties(out); getSkybox().listChangedProperties(out); - getStage().listChangedProperties(out); getHaze().listChangedProperties(out); return out; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 4887b42138..d0dabbf5b6 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -41,12 +41,18 @@ #include "SimulationOwner.h" #include "SkyboxPropertyGroup.h" #include "HazePropertyGroup.h" -#include "StagePropertyGroup.h" #include "TextEntityItem.h" #include "ZoneEntityItem.h" const quint64 UNKNOWN_CREATED_TIME = 0; +using ComponentPair = std::pair; +const std::array COMPONENT_MODES = { { + ComponentPair { COMPONENT_MODE_INHERIT, { "inherit" } }, + ComponentPair { COMPONENT_MODE_DISABLED, { "disabled" } }, + ComponentPair { COMPONENT_MODE_ENABLED, { "enabled" } } +} }; + /// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an /// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete /// set of entity item properties via JavaScript hashes/QScriptValues @@ -173,13 +179,17 @@ public: DEFINE_PROPERTY(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float, particle::DEFAULT_RADIUS_FINISH); DEFINE_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, EmitterShouldTrail, emitterShouldTrail, bool, particle::DEFAULT_EMITTER_SHOULD_TRAIL); DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup); + DEFINE_PROPERTY_GROUP(AmbientLight, ambientLight, AmbientLightPropertyGroup); DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE); DEFINE_PROPERTY_REF(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray, PolyVoxEntityItem::DEFAULT_VOXEL_DATA); DEFINE_PROPERTY_REF(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t, PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE); DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString, ENTITY_ITEM_DEFAULT_NAME); - DEFINE_PROPERTY_REF_ENUM(PROP_BACKGROUND_MODE, BackgroundMode, backgroundMode, BackgroundMode, BACKGROUND_MODE_INHERIT); - DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup); + DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); + DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); + DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_ENABLED); + + // This is the default mode for zone creation DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); @@ -248,9 +258,10 @@ public: DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS); - static QString getBackgroundModeString(BackgroundMode mode); - static QString getHazeModeString(uint32_t mode); + static QString getComponentModeString(uint32_t mode); + static QString getComponentModeAsString(uint32_t mode); + std::array::const_iterator findComponent(const QString& mode); public: float getMaxDimension() const { return glm::compMax(_dimensions); } @@ -478,8 +489,11 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, CertificateID, certificateID, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, StaticCertificateVersion, staticCertificateVersion, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, HazeMode, hazeMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, KeyLightMode, keyLightMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, AmbientLightMode, ambientLightMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, SkyboxMode, skyboxMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index d515d60535..7fd72bf0ee 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -220,6 +220,10 @@ enum EntityPropertyList { PROP_HAZE_KEYLIGHT_RANGE, PROP_HAZE_KEYLIGHT_ALTITUDE, + PROP_KEY_LIGHT_MODE, + PROP_AMBIENT_LIGHT_MODE, + PROP_SKYBOX_MODE, + PROP_LOCAL_DIMENSIONS, // only used to convert values to and from scripts //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -243,21 +247,14 @@ enum EntityPropertyList { // the size of the properties bitflags mask PROP_KEYLIGHT_COLOR = PROP_COLOR, PROP_KEYLIGHT_INTENSITY = PROP_INTENSITY, - PROP_KEYLIGHT_AMBIENT_INTENSITY = PROP_CUTOFF, PROP_KEYLIGHT_DIRECTION = PROP_EXPONENT, - PROP_STAGE_SUN_MODEL_ENABLED = PROP_IS_SPOTLIGHT, - PROP_STAGE_LATITUDE = PROP_DIFFUSE_COLOR, - PROP_STAGE_LONGITUDE = PROP_AMBIENT_COLOR_UNUSED, - PROP_STAGE_ALTITUDE = PROP_SPECULAR_COLOR_UNUSED, - PROP_STAGE_DAY = PROP_LINEAR_ATTENUATION_UNUSED, - PROP_STAGE_HOUR = PROP_QUADRATIC_ATTENUATION_UNUSED, - PROP_STAGE_AUTOMATIC_HOURDAY = PROP_ANIMATION_FRAME_INDEX, - PROP_BACKGROUND_MODE = PROP_MODEL_URL, PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, PROP_SKYBOX_URL = PROP_ANIMATION_FPS, - PROP_KEYLIGHT_AMBIENT_URL = PROP_ANIMATION_PLAYING, - + + PROP_AMBIENT_LIGHT_INTENSITY = PROP_CUTOFF, + PROP_AMBIENT_LIGHT_URL = PROP_ANIMATION_PLAYING, + // Aliases/Piggyback properties for Web. These properties intentionally reuse the enum values for // other properties which will never overlap with each other. PROP_SOURCE_URL = PROP_MODEL_URL, diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 794e56b262..ae243623f6 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2281,6 +2281,31 @@ bool EntityTree::readFromMap(QVariantMap& map) { properties.setOwningAvatarID(myNodeID); } + // TEMPORARY fix for older content not containing these fields in the zones + if (properties.getType() == EntityTypes::EntityType::Zone) { + if (!entityMap.contains("keyLightMode")) { + properties.setKeyLightMode(COMPONENT_MODE_ENABLED); + } + + if (!entityMap.contains("skyboxMode")) { + if (entityMap.contains("backgroundMode") && (entityMap["backgroundMode"].toString() == "nothing")) { + properties.setSkyboxMode(COMPONENT_MODE_INHERIT); + } else { + // Either the background mode field is missing (shouldn't happen) or the background mode is "skybox" + properties.setSkyboxMode(COMPONENT_MODE_ENABLED); + + // Copy the skybox URL if the ambient URL is empty, as this is the legacy behaviour + if (properties.getAmbientLight().getAmbientURL() == "") { + properties.getAmbientLight().setAmbientURL(properties.getSkybox().getURL()); + } + } + } + + if (!entityMap.contains("ambientLightMode")) { + properties.setAmbientLightMode(COMPONENT_MODE_ENABLED); + } + } + EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index 4246da309b..c476b4c23c 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -17,54 +17,41 @@ #include "EntityItemProperties.h" #include "EntityItemPropertiesMacros.h" - const xColor KeyLightPropertyGroup::DEFAULT_KEYLIGHT_COLOR = { 255, 255, 255 }; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_INTENSITY = 1.0f; const float KeyLightPropertyGroup::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY = 0.5f; const glm::vec3 KeyLightPropertyGroup::DEFAULT_KEYLIGHT_DIRECTION = { 0.0f, -1.0f, 0.0f }; -void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { - +void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, + QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_COLOR, KeyLight, keyLight, Color, color); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_INTENSITY, KeyLight, keyLight, Intensity, intensity); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLight, keyLight, AmbientIntensity, ambientIntensity); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_DIRECTION, KeyLight, keyLight, Direction, direction); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_KEYLIGHT_AMBIENT_URL, KeyLight, keyLight, AmbientURL, ambientURL); - } void KeyLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, color, xColor, setColor); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, intensity, float, setIntensity); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, ambientIntensity, float, setAmbientIntensity); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, direction, glmVec3, setDirection); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, ambientURL, QString, setAmbientURL); // legacy property support COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightColor, xColor, setColor, getColor); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightIntensity, float, setIntensity, getIntensity); - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightAmbientIntensity, float, setAmbientIntensity, getAmbientIntensity); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightDirection, glmVec3, setDirection, getDirection); } void KeyLightPropertyGroup::merge(const KeyLightPropertyGroup& other) { COPY_PROPERTY_IF_CHANGED(color); COPY_PROPERTY_IF_CHANGED(intensity); - COPY_PROPERTY_IF_CHANGED(ambientIntensity); COPY_PROPERTY_IF_CHANGED(direction); - COPY_PROPERTY_IF_CHANGED(ambientURL); } - - void KeyLightPropertyGroup::debugDump() const { qCDebug(entities) << " KeyLightPropertyGroup: ---------------------------------------------"; qCDebug(entities) << " color:" << getColor(); // << "," << getColor()[1] << "," << getColor()[2]; qCDebug(entities) << " intensity:" << getIntensity(); qCDebug(entities) << " direction:" << getDirection(); - qCDebug(entities) << " ambientIntensity:" << getAmbientIntensity(); - qCDebug(entities) << " ambientURL:" << getAmbientURL(); } void KeyLightPropertyGroup::listChangedProperties(QList& out) { @@ -77,52 +64,38 @@ void KeyLightPropertyGroup::listChangedProperties(QList& out) { if (directionChanged()) { out << "keyLight-direction"; } - if (ambientIntensityChanged()) { - out << "keyLight-ambientIntensity"; - } - if (ambientURLChanged()) { - out << "keyLight-ambientURL"; - } } - bool KeyLightPropertyGroup::appendToEditPacket(OctreePacketData* packetData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, getAmbientIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_URL, getAmbientURL()); return true; } - -bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes) { - +bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, + int& processedBytes) { + int bytesRead = 0; bool overwriteLocalData = true; bool somethingChanged = false; READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, float, setAmbientIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_URL, QString, setAmbientURL); - - + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_COLOR, Color); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_INTENSITY, Intensity); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_AMBIENT_INTENSITY, AmbientIntensity); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_DIRECTION, Direction); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_KEYLIGHT_AMBIENT_URL, AmbientURL); processedBytes += bytesRead; @@ -134,9 +107,7 @@ bool KeyLightPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFl void KeyLightPropertyGroup::markAllChanged() { _colorChanged = true; _intensityChanged = true; - _ambientIntensityChanged = true; _directionChanged = true; - _ambientURLChanged = true; } EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const { @@ -144,20 +115,15 @@ EntityPropertyFlags KeyLightPropertyGroup::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_COLOR, color); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_INTENSITY, intensity); - CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_AMBIENT_INTENSITY, ambientIntensity); CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, direction); - CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_AMBIENT_URL, ambientURL); return changedProperties; } -void KeyLightPropertyGroup::getProperties(EntityItemProperties& properties) const { - +void KeyLightPropertyGroup::getProperties(EntityItemProperties& properties) const { COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Color, getColor); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Intensity, getIntensity); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, AmbientIntensity, getAmbientIntensity); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, Direction, getDirection); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(KeyLight, AmbientURL, getAmbientURL); } bool KeyLightPropertyGroup::setProperties(const EntityItemProperties& properties) { @@ -165,9 +131,7 @@ bool KeyLightPropertyGroup::setProperties(const EntityItemProperties& properties SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Color, color, setColor); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Intensity, intensity, setIntensity); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, AmbientIntensity, ambientIntensity, setAmbientIntensity); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, Direction, direction, setDirection); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(KeyLight, AmbientURL, ambientURL, setAmbientURL); return somethingChanged; } @@ -177,9 +141,7 @@ EntityPropertyFlags KeyLightPropertyGroup::getEntityProperties(EncodeBitstreamPa requestedProperties += PROP_KEYLIGHT_COLOR; requestedProperties += PROP_KEYLIGHT_INTENSITY; - requestedProperties += PROP_KEYLIGHT_AMBIENT_INTENSITY; requestedProperties += PROP_KEYLIGHT_DIRECTION; - requestedProperties += PROP_KEYLIGHT_AMBIENT_URL; return requestedProperties; } @@ -196,9 +158,7 @@ void KeyLightPropertyGroup::appendSubclassData(OctreePacketData* packetData, Enc APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, getIntensity()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, getAmbientIntensity()); APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, getDirection()); - APPEND_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_URL, getAmbientURL()); } int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, @@ -211,9 +171,7 @@ int KeyLightPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* READ_ENTITY_PROPERTY(PROP_KEYLIGHT_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_INTENSITY, float, setIntensity); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, float, setAmbientIntensity); READ_ENTITY_PROPERTY(PROP_KEYLIGHT_DIRECTION, glm::vec3, setDirection); - READ_ENTITY_PROPERTY(PROP_KEYLIGHT_AMBIENT_URL, QString, setAmbientURL); return bytesRead; } diff --git a/libraries/entities/src/KeyLightPropertyGroup.h b/libraries/entities/src/KeyLightPropertyGroup.h index 210d410bd9..f33ebb282d 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.h +++ b/libraries/entities/src/KeyLightPropertyGroup.h @@ -81,9 +81,7 @@ public: DEFINE_PROPERTY_REF(PROP_KEYLIGHT_COLOR, Color, color, xColor, DEFAULT_KEYLIGHT_COLOR); DEFINE_PROPERTY(PROP_KEYLIGHT_INTENSITY, Intensity, intensity, float, DEFAULT_KEYLIGHT_INTENSITY); - DEFINE_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, AmbientIntensity, ambientIntensity, float, DEFAULT_KEYLIGHT_AMBIENT_INTENSITY); DEFINE_PROPERTY_REF(PROP_KEYLIGHT_DIRECTION, Direction, direction, glm::vec3, DEFAULT_KEYLIGHT_DIRECTION); - DEFINE_PROPERTY_REF(PROP_KEYLIGHT_AMBIENT_URL, AmbientURL, ambientURL, QString, ""); }; #endif // hifi_KeyLightPropertyGroup_h diff --git a/libraries/entities/src/StagePropertyGroup.cpp b/libraries/entities/src/StagePropertyGroup.cpp deleted file mode 100644 index cd486c96ac..0000000000 --- a/libraries/entities/src/StagePropertyGroup.cpp +++ /dev/null @@ -1,283 +0,0 @@ -// -// StagePropertyGroup.cpp -// libraries/entities/src -// -// Created by Brad Hefta-Gaub on 12/4/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include - -#include - -#include "StagePropertyGroup.h" -#include "EntityItemProperties.h" -#include "EntityItemPropertiesMacros.h" - -const bool StagePropertyGroup::DEFAULT_STAGE_SUN_MODEL_ENABLED = false; -const float StagePropertyGroup::DEFAULT_STAGE_LATITUDE = 37.777f; -const float StagePropertyGroup::DEFAULT_STAGE_LONGITUDE = 122.407f; -const float StagePropertyGroup::DEFAULT_STAGE_ALTITUDE = 0.03f; -const quint16 StagePropertyGroup::DEFAULT_STAGE_DAY = 60; -const float StagePropertyGroup::DEFAULT_STAGE_HOUR = 12.0f; - -void StagePropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_STAGE_SUN_MODEL_ENABLED, Stage, stage, SunModelEnabled, sunModelEnabled); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_STAGE_LATITUDE, Stage, stage, Latitude, latitude); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_STAGE_LONGITUDE, Stage, stage, Longitude, longitude); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_STAGE_ALTITUDE, Stage, stage, Altitude, altitude); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_STAGE_DAY, Stage, stage, Day, day); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_STAGE_HOUR, Stage, stage, Hour, hour); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_STAGE_AUTOMATIC_HOURDAY, Stage, stage, AutomaticHourDay, automaticHourDay); -} - -void StagePropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { - - // Backward compatibility support for the old way of doing stage properties - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(stageSunModelEnabled, bool, setSunModelEnabled, getSunModelEnabled); - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(stageLatitude, float, setLatitude, getLatitude); - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(stageLongitude, float, setLongitude, getLongitude); - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(stageAltitude, float, setAltitude, getAltitude); - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(stageDay, uint16_t, setDay, getDay); - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(stageHour, float, setHour, getHour); - - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(stage, sunModelEnabled, bool, setSunModelEnabled); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(stage, latitude, float, setLatitude); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(stage, longitude, float, setLongitude); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(stage, altitude, float, setAltitude); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(stage, day, uint16_t, setDay); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(stage, hour, float, setHour); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(stage, automaticHourDay, bool, setAutomaticHourDay); -} - -void StagePropertyGroup::merge(const StagePropertyGroup& other) { - COPY_PROPERTY_IF_CHANGED(sunModelEnabled); - COPY_PROPERTY_IF_CHANGED(latitude); - COPY_PROPERTY_IF_CHANGED(longitude); - COPY_PROPERTY_IF_CHANGED(altitude); - COPY_PROPERTY_IF_CHANGED(day); - COPY_PROPERTY_IF_CHANGED(hour); - COPY_PROPERTY_IF_CHANGED(automaticHourDay); -} - - -void StagePropertyGroup::debugDump() const { - qCDebug(entities) << " StagePropertyGroup: ---------------------------------------------"; - qCDebug(entities) << " _sunModelEnabled:" << _sunModelEnabled; - qCDebug(entities) << " _latitude:" << _latitude; - qCDebug(entities) << " _longitude:" << _longitude; - qCDebug(entities) << " _altitude:" << _altitude; - qCDebug(entities) << " _day:" << _day; - qCDebug(entities) << " _hour:" << _hour; - qCDebug(entities) << " _automaticHourDay:" << _automaticHourDay; -} - -void StagePropertyGroup::listChangedProperties(QList& out) { - if (sunModelEnabledChanged()) { - out << "stage-sunModelEnabled"; - } - if (latitudeChanged()) { - out << "stage-latitude"; - } - if (altitudeChanged()) { - out << "stage-altitude"; - } - if (dayChanged()) { - out << "stage-day"; - } - if (hourChanged()) { - out << "stage-hour"; - } - if (automaticHourDayChanged()) { - out << "stage-automaticHourDay"; - } -} - -bool StagePropertyGroup::appendToEditPacket(OctreePacketData* packetData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - - APPEND_ENTITY_PROPERTY(PROP_STAGE_SUN_MODEL_ENABLED, getSunModelEnabled()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_LATITUDE, getLatitude()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_LONGITUDE, getLongitude()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_ALTITUDE, getAltitude()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_DAY, getDay()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_HOUR, getHour()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_AUTOMATIC_HOURDAY, getAutomaticHourDay()); - - return true; -} - - -bool StagePropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes) { - - int bytesRead = 0; - bool overwriteLocalData = true; - bool somethingChanged = false; - - READ_ENTITY_PROPERTY(PROP_STAGE_SUN_MODEL_ENABLED, bool, setSunModelEnabled); - READ_ENTITY_PROPERTY(PROP_STAGE_LATITUDE, float, setLatitude); - READ_ENTITY_PROPERTY(PROP_STAGE_LONGITUDE, float, setLongitude); - READ_ENTITY_PROPERTY(PROP_STAGE_ALTITUDE, float, setAltitude); - READ_ENTITY_PROPERTY(PROP_STAGE_DAY, quint16, setDay); - READ_ENTITY_PROPERTY(PROP_STAGE_HOUR, float, setHour); - READ_ENTITY_PROPERTY(PROP_STAGE_AUTOMATIC_HOURDAY, bool, setAutomaticHourDay); - - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_STAGE_SUN_MODEL_ENABLED, SunModelEnabled); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_STAGE_LATITUDE, Latitude); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_STAGE_LONGITUDE, Longitude); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_STAGE_ALTITUDE, Altitude); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_STAGE_DAY, Day); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_STAGE_HOUR, Hour); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_STAGE_AUTOMATIC_HOURDAY, AutomaticHourDay); - - processedBytes += bytesRead; - - Q_UNUSED(somethingChanged); - - return true; -} - -void StagePropertyGroup::markAllChanged() { - _sunModelEnabledChanged = true; - _latitudeChanged = true; - _longitudeChanged = true; - _altitudeChanged = true; - _dayChanged = true; - _hourChanged = true; - _automaticHourDayChanged = true; -} - -EntityPropertyFlags StagePropertyGroup::getChangedProperties() const { - EntityPropertyFlags changedProperties; - - CHECK_PROPERTY_CHANGE(PROP_STAGE_SUN_MODEL_ENABLED, sunModelEnabled); - CHECK_PROPERTY_CHANGE(PROP_STAGE_LATITUDE, latitude); - CHECK_PROPERTY_CHANGE(PROP_STAGE_LONGITUDE, longitude); - CHECK_PROPERTY_CHANGE(PROP_STAGE_ALTITUDE, altitude); - CHECK_PROPERTY_CHANGE(PROP_STAGE_DAY, day); - CHECK_PROPERTY_CHANGE(PROP_STAGE_HOUR, hour); - CHECK_PROPERTY_CHANGE(PROP_STAGE_AUTOMATIC_HOURDAY, automaticHourDay); - - return changedProperties; -} - -void StagePropertyGroup::getProperties(EntityItemProperties& properties) const { - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Stage, SunModelEnabled, getSunModelEnabled); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Stage, Latitude, getLatitude); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Stage, Longitude, getLongitude); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Stage, Altitude, getAltitude); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Stage, Day, getDay); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Stage, Hour, getHour); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Stage, AutomaticHourDay, getAutomaticHourDay); -} - -bool StagePropertyGroup::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = false; - - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Stage, SunModelEnabled, sunModelEnabled, setSunModelEnabled); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Stage, Latitude, latitude, setLatitude); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Stage, Longitude, longitude, setLongitude); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Stage, Altitude, altitude, setAltitude); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Stage, Day, day, setDay); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Stage, Hour, hour, setHour); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Stage, AutomaticHourDay, automaticHourDay, setAutomaticHourDay); - - return somethingChanged; -} - -EntityPropertyFlags StagePropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties; - - requestedProperties += PROP_STAGE_SUN_MODEL_ENABLED; - requestedProperties += PROP_STAGE_LATITUDE; - requestedProperties += PROP_STAGE_LONGITUDE; - requestedProperties += PROP_STAGE_ALTITUDE; - requestedProperties += PROP_STAGE_DAY; - requestedProperties += PROP_STAGE_HOUR; - requestedProperties += PROP_STAGE_AUTOMATIC_HOURDAY; - - return requestedProperties; -} - -void StagePropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - - APPEND_ENTITY_PROPERTY(PROP_STAGE_SUN_MODEL_ENABLED, getSunModelEnabled()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_LATITUDE, getLatitude()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_LONGITUDE, getLongitude()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_ALTITUDE, getAltitude()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_DAY, getDay()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_HOUR, getHour()); - APPEND_ENTITY_PROPERTY(PROP_STAGE_AUTOMATIC_HOURDAY, getAutomaticHourDay()); -} - -int StagePropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_STAGE_SUN_MODEL_ENABLED, bool, setSunModelEnabled); - READ_ENTITY_PROPERTY(PROP_STAGE_LATITUDE, float, setLatitude); - READ_ENTITY_PROPERTY(PROP_STAGE_LONGITUDE, float, setLongitude); - READ_ENTITY_PROPERTY(PROP_STAGE_ALTITUDE, float, setAltitude); - READ_ENTITY_PROPERTY(PROP_STAGE_DAY, quint16, setDay); - READ_ENTITY_PROPERTY(PROP_STAGE_HOUR, float, setHour); - READ_ENTITY_PROPERTY(PROP_STAGE_AUTOMATIC_HOURDAY, bool, setAutomaticHourDay); - - return bytesRead; -} - -static const float TOTAL_LONGITUDES = 360.0f; -static const float HOURS_PER_DAY = 24; -static const float SECONDS_PER_DAY = 60 * 60 * HOURS_PER_DAY; -static const float MSECS_PER_DAY = SECONDS_PER_DAY * MSECS_PER_SECOND; - -float StagePropertyGroup::calculateHour() const { - if (!_automaticHourDay) { - return _hour; - } - - QDateTime utc(QDateTime::currentDateTimeUtc()); - float adjustFromUTC = (_longitude / TOTAL_LONGITUDES); - float offsetFromUTCinMsecs = adjustFromUTC * MSECS_PER_DAY; - int msecsSinceStartOfDay = utc.time().msecsSinceStartOfDay(); - float calutatedHour = ((msecsSinceStartOfDay + offsetFromUTCinMsecs) / MSECS_PER_DAY) * HOURS_PER_DAY; - - // calculate hour based on longitude and time from GMT - return calutatedHour; -} - -uint16_t StagePropertyGroup::calculateDay() const { - - if (!_automaticHourDay) { - return _day; - } - - QDateTime utc(QDateTime::currentDateTimeUtc()); - int calutatedDay = utc.date().dayOfYear(); - - // calculate day based on longitude and time from GMT - return calutatedDay; -} - diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index fbfbc71a85..107e7cf561 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -42,8 +42,6 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en _shapeType = DEFAULT_SHAPE_TYPE; _compoundShapeURL = DEFAULT_COMPOUND_SHAPE_URL; - - _backgroundMode = BACKGROUND_MODE_INHERIT; } EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { @@ -53,12 +51,13 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr withReadLock([&] { _keyLightProperties.getProperties(properties); }); - - _stageProperties.getProperties(properties); + + withReadLock([&] { + _ambientLightProperties.getProperties(properties); + }); COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundMode, getBackgroundMode); // Contains a QString property, must be synchronized withReadLock([&] { @@ -72,6 +71,10 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode); _hazeProperties.getProperties(properties); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightMode, getKeyLightMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(ambientLightMode, getAmbientLightMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(skyboxMode, getSkyboxMode); + return properties; } @@ -100,12 +103,12 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie withWriteLock([&] { _keyLightPropertiesChanged = _keyLightProperties.setProperties(properties); }); - - _stagePropertiesChanged = _stageProperties.setProperties(properties); + withWriteLock([&] { + _ambientLightPropertiesChanged = _ambientLightProperties.setProperties(properties); + }); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode); // Contains a QString property, must be synchronized withWriteLock([&] { @@ -115,11 +118,16 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); - + SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); _hazePropertiesChanged = _hazeProperties.setProperties(properties); - somethingChanged = somethingChanged || _keyLightPropertiesChanged || _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged; + SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightMode, setKeyLightMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientLightMode, setAmbientLightMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode); + + somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || + _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged; return somethingChanged; } @@ -141,15 +149,18 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromKeylight; dataAt += bytesFromKeylight; - int bytesFromStage = _stageProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, _stagePropertiesChanged); - somethingChanged = somethingChanged || _stagePropertiesChanged; - bytesRead += bytesFromStage; - dataAt += bytesFromStage; + int bytesFromAmbientlight; + withWriteLock([&] { + bytesFromAmbientlight = _ambientLightProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, _ambientLightPropertiesChanged); + }); + + somethingChanged = somethingChanged || _ambientLightPropertiesChanged; + bytesRead += bytesFromAmbientlight; + dataAt += bytesFromAmbientlight; READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); - READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode); int bytesFromSkybox; withWriteLock([&] { @@ -173,6 +184,10 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromHaze; dataAt += bytesFromHaze; + READ_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); + READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); + READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); + return bytesRead; } @@ -185,11 +200,12 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += _keyLightProperties.getEntityProperties(params); }); - requestedProperties += _stageProperties.getEntityProperties(params); + withReadLock([&] { + requestedProperties += _ambientLightProperties.getEntityProperties(params); + }); requestedProperties += PROP_SHAPE_TYPE; requestedProperties += PROP_COMPOUND_SHAPE_URL; - requestedProperties += PROP_BACKGROUND_MODE; withReadLock([&] { requestedProperties += _skyboxProperties.getEntityProperties(params); @@ -202,6 +218,10 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_HAZE_MODE; requestedProperties += _hazeProperties.getEntityProperties(params); + requestedProperties += PROP_KEY_LIGHT_MODE; + requestedProperties += PROP_AMBIENT_LIGHT_MODE; + requestedProperties += PROP_SKYBOX_MODE; + return requestedProperties; } @@ -216,15 +236,13 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits bool successPropertyFits = true; _keyLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); - - _stageProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); + propertyFlags, propertiesDidntFit, propertyCount, appendState); + _ambientLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); - APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, (uint32_t)getBackgroundMode()); // could this be a uint16?? _skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -236,6 +254,10 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); _hazeProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + + APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)getKeyLightMode()); + APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)getAmbientLightMode()); + APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode()); } void ZoneEntityItem::debugDump() const { @@ -244,13 +266,15 @@ void ZoneEntityItem::debugDump() const { qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " _backgroundMode:" << EntityItemProperties::getBackgroundModeString(_backgroundMode); - qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getHazeModeString(_hazeMode); + qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getComponentModeString(_hazeMode); + qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getComponentModeString(_keyLightMode); + qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeString(_ambientLightMode); + qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeString(_skyboxMode); _keyLightProperties.debugDump(); + _ambientLightProperties.debugDump(); _skyboxProperties.debugDump(); _hazeProperties.debugDump(); - _stageProperties.debugDump(); } ShapeType ZoneEntityItem::getShapeType() const { @@ -313,7 +337,7 @@ QString ZoneEntityItem::getCompoundShapeURL() const { void ZoneEntityItem::resetRenderingPropertiesChanged() { withWriteLock([&] { _keyLightPropertiesChanged = false; - _backgroundPropertiesChanged = false; + _ambientLightPropertiesChanged = false; _skyboxPropertiesChanged = false; _hazePropertiesChanged = false; _stagePropertiesChanged = false; @@ -321,7 +345,7 @@ void ZoneEntityItem::resetRenderingPropertiesChanged() { } void ZoneEntityItem::setHazeMode(const uint32_t value) { - if (value < COMPONENT_MODE_ITEM_COUNT) { + if (value < COMPONENT_MODE_ITEM_COUNT && value != _hazeMode) { _hazeMode = value; _hazePropertiesChanged = true; } @@ -330,3 +354,36 @@ void ZoneEntityItem::setHazeMode(const uint32_t value) { uint32_t ZoneEntityItem::getHazeMode() const { return _hazeMode; } + +void ZoneEntityItem::setKeyLightMode(const uint32_t value) { + if (value < COMPONENT_MODE_ITEM_COUNT && value != _keyLightMode) { + _keyLightMode = value; + _keyLightPropertiesChanged = true; + } +} + +uint32_t ZoneEntityItem::getKeyLightMode() const { + return _keyLightMode; +} + +void ZoneEntityItem::setAmbientLightMode(const uint32_t value) { + if (value < COMPONENT_MODE_ITEM_COUNT && value != _ambientLightMode) { + _ambientLightMode = value; + _ambientLightPropertiesChanged = true; + } +} + +uint32_t ZoneEntityItem::getAmbientLightMode() const { + return _ambientLightMode; +} + +void ZoneEntityItem::setSkyboxMode(const uint32_t value) { + if (value < COMPONENT_MODE_ITEM_COUNT && value != _skyboxMode) { + _skyboxMode = value; + _skyboxPropertiesChanged = true; + } +} + +uint32_t ZoneEntityItem::getSkyboxMode() const { + return _skyboxMode; +} diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 46e8a00c24..05dcc05416 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -13,11 +13,11 @@ #define hifi_ZoneEntityItem_h #include "KeyLightPropertyGroup.h" +#include "AmbientLightPropertyGroup.h" #include "EntityItem.h" #include "EntityTree.h" #include "SkyboxPropertyGroup.h" #include "HazePropertyGroup.h" -#include "StagePropertyGroup.h" #include class ZoneEntityItem : public EntityItem { @@ -66,19 +66,24 @@ public: virtual void setCompoundShapeURL(const QString& url); KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock([&] { return _keyLightProperties; }); } - - void setBackgroundMode(BackgroundMode value) { _backgroundMode = value; _backgroundPropertiesChanged = true; } - BackgroundMode getBackgroundMode() const { return _backgroundMode; } + AmbientLightPropertyGroup getAmbientLightProperties() const { return resultWithReadLock([&] { return _ambientLightProperties; }); } void setHazeMode(const uint32_t value); uint32_t getHazeMode() const; + void setKeyLightMode(uint32_t value); + uint32_t getKeyLightMode() const; + + void setAmbientLightMode(uint32_t value); + uint32_t getAmbientLightMode() const; + + void setSkyboxMode(uint32_t value); + uint32_t getSkyboxMode() const; + SkyboxPropertyGroup getSkyboxProperties() const { return resultWithReadLock([&] { return _skyboxProperties; }); } const HazePropertyGroup& getHazeProperties() const { return _hazeProperties; } - const StagePropertyGroup& getStageProperties() const { return _stageProperties; } - bool getFlyingAllowed() const { return _flyingAllowed; } void setFlyingAllowed(bool value) { _flyingAllowed = value; } bool getGhostingAllowed() const { return _ghostingAllowed; } @@ -87,7 +92,7 @@ public: void setFilterURL(const QString url); bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; } - bool backgroundPropertiesChanged() const { return _backgroundPropertiesChanged; } + bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; } bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; } bool hazePropertiesChanged() const { @@ -112,21 +117,22 @@ public: static const bool DEFAULT_GHOSTING_ALLOWED; static const QString DEFAULT_FILTER_URL; - static const uint32_t DEFAULT_HAZE_MODE{ (uint32_t)COMPONENT_MODE_INHERIT }; - protected: KeyLightPropertyGroup _keyLightProperties; + AmbientLightPropertyGroup _ambientLightProperties; ShapeType _shapeType = DEFAULT_SHAPE_TYPE; QString _compoundShapeURL; - BackgroundMode _backgroundMode = BACKGROUND_MODE_INHERIT; + // The following 3 values are the defaults for zone creation + uint32_t _keyLightMode { COMPONENT_MODE_INHERIT }; + uint32_t _skyboxMode { COMPONENT_MODE_INHERIT }; + uint32_t _ambientLightMode { COMPONENT_MODE_INHERIT }; - uint32_t _hazeMode{ DEFAULT_HAZE_MODE }; + uint32_t _hazeMode { COMPONENT_MODE_INHERIT }; SkyboxPropertyGroup _skyboxProperties; HazePropertyGroup _hazeProperties; - StagePropertyGroup _stageProperties; bool _flyingAllowed { DEFAULT_FLYING_ALLOWED }; bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; @@ -134,7 +140,7 @@ protected: // Dirty flags turn true when either keylight properties is changing values. bool _keyLightPropertiesChanged { false }; - bool _backgroundPropertiesChanged{ false }; + bool _ambientLightPropertiesChanged { false }; bool _skyboxPropertiesChanged { false }; bool _hazePropertiesChanged{ false }; bool _stagePropertiesChanged { false }; diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index 6f771a702a..50d40c35ac 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -60,6 +60,7 @@ public: QVector indices; QVector vertices; QVector normals; + QVector tangents; }; struct FBXJointShapeInfo { diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index c52647802e..659d6dfa1e 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -303,17 +303,97 @@ FBXBlendshape extractBlendshape(const FBXNode& object) { return blendshape; } +using IndexAccessor = std::function; -void setTangents(FBXMesh& mesh, int firstIndex, int secondIndex) { - const glm::vec3& normal = mesh.normals.at(firstIndex); - glm::vec3 bitangent = glm::cross(normal, mesh.vertices.at(secondIndex) - mesh.vertices.at(firstIndex)); - if (glm::length(bitangent) < EPSILON) { - return; +static void setTangents(const FBXMesh& mesh, const IndexAccessor& vertexAccessor, int firstIndex, int secondIndex, + const QVector& vertices, const QVector& normals, QVector& tangents) { + glm::vec3 vertex[2]; + glm::vec3 normal; + glm::vec3* tangent = vertexAccessor(mesh, firstIndex, secondIndex, vertex, normal); + if (tangent) { + glm::vec3 bitangent = glm::cross(normal, vertex[1] - vertex[0]); + if (glm::length(bitangent) < EPSILON) { + return; + } + glm::vec2 texCoordDelta = mesh.texCoords.at(secondIndex) - mesh.texCoords.at(firstIndex); + glm::vec3 normalizedNormal = glm::normalize(normal); + *tangent += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * + glm::normalize(bitangent), normalizedNormal); } - glm::vec2 texCoordDelta = mesh.texCoords.at(secondIndex) - mesh.texCoords.at(firstIndex); - glm::vec3 normalizedNormal = glm::normalize(normal); - mesh.tangents[firstIndex] += glm::cross(glm::angleAxis(-atan2f(-texCoordDelta.t, texCoordDelta.s), normalizedNormal) * - glm::normalize(bitangent), normalizedNormal); +} + +static void createTangents(const FBXMesh& mesh, bool generateFromTexCoords, + const QVector& vertices, const QVector& normals, QVector& tangents, + IndexAccessor accessor) { + // if we have a normal map (and texture coordinates), we must compute tangents + if (generateFromTexCoords && !mesh.texCoords.isEmpty()) { + tangents.resize(vertices.size()); + + foreach(const FBXMeshPart& part, mesh.parts) { + for (int i = 0; i < part.quadIndices.size(); i += 4) { + setTangents(mesh, accessor, part.quadIndices.at(i), part.quadIndices.at(i + 1), vertices, normals, tangents); + setTangents(mesh, accessor, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2), vertices, normals, tangents); + setTangents(mesh, accessor, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3), vertices, normals, tangents); + setTangents(mesh, accessor, part.quadIndices.at(i + 3), part.quadIndices.at(i), vertices, normals, tangents); + } + // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 + // This is most likely evidence of a further problem in extractMesh() + for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { + setTangents(mesh, accessor, part.triangleIndices.at(i), part.triangleIndices.at(i + 1), vertices, normals, tangents); + setTangents(mesh, accessor, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2), vertices, normals, tangents); + setTangents(mesh, accessor, part.triangleIndices.at(i + 2), part.triangleIndices.at(i), vertices, normals, tangents); + } + if ((part.triangleIndices.size() % 3) != 0) { + qCDebug(modelformat) << "Error in extractFBXGeometry part.triangleIndices.size() is not divisible by three "; + } + } + } +} + +static void createMeshTangents(FBXMesh& mesh, bool generateFromTexCoords) { + // This is the only workaround I've found to trick the compiler into understanding that mesh.tangents isn't + // const in the lambda function. + auto& tangents = mesh.tangents; + createTangents(mesh, generateFromTexCoords, mesh.vertices, mesh.normals, mesh.tangents, + [&](const FBXMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { + outVertices[0] = mesh.vertices[firstIndex]; + outVertices[1] = mesh.vertices[secondIndex]; + outNormal = mesh.normals[firstIndex]; + return &(tangents[firstIndex]); + }); +} + +static void createBlendShapeTangents(FBXMesh& mesh, bool generateFromTexCoords, FBXBlendshape& blendShape) { + // Create lookup to get index in blend shape from vertex index in mesh + std::vector reverseIndices; + reverseIndices.resize(mesh.vertices.size()); + std::iota(reverseIndices.begin(), reverseIndices.end(), 0); + + for (int indexInBlendShape = 0; indexInBlendShape < blendShape.indices.size(); ++indexInBlendShape) { + auto indexInMesh = blendShape.indices[indexInBlendShape]; + reverseIndices[indexInMesh] = indexInBlendShape; + } + + createTangents(mesh, generateFromTexCoords, blendShape.vertices, blendShape.normals, blendShape.tangents, + [&](const FBXMesh& mesh, int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec3& outNormal) { + const auto index1 = reverseIndices[firstIndex]; + const auto index2 = reverseIndices[secondIndex]; + + if (index1 < blendShape.vertices.size()) { + outVertices[0] = blendShape.vertices[index1]; + if (index2 < blendShape.vertices.size()) { + outVertices[1] = blendShape.vertices[index2]; + } else { + // Index isn't in the blend shape so return vertex from mesh + outVertices[1] = mesh.vertices[secondIndex]; + } + outNormal = blendShape.normals[index1]; + return &blendShape.tangents[index1]; + } else { + // Index isn't in blend shape so return nullptr + return (glm::vec3*)nullptr; + } + }); } QVector getIndices(const QVector ids, QVector modelIDs) { @@ -1570,27 +1650,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } - // if we have a normal map (and texture coordinates), we must compute tangents - if (generateTangents && !extracted.mesh.texCoords.isEmpty()) { - extracted.mesh.tangents.resize(extracted.mesh.vertices.size()); - foreach (const FBXMeshPart& part, extracted.mesh.parts) { - for (int i = 0; i < part.quadIndices.size(); i += 4) { - setTangents(extracted.mesh, part.quadIndices.at(i), part.quadIndices.at(i + 1)); - setTangents(extracted.mesh, part.quadIndices.at(i + 1), part.quadIndices.at(i + 2)); - setTangents(extracted.mesh, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3)); - setTangents(extracted.mesh, part.quadIndices.at(i + 3), part.quadIndices.at(i)); - } - // <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0 - // This is most likely evidence of a further problem in extractMesh() - for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) { - setTangents(extracted.mesh, part.triangleIndices.at(i), part.triangleIndices.at(i + 1)); - setTangents(extracted.mesh, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2)); - setTangents(extracted.mesh, part.triangleIndices.at(i + 2), part.triangleIndices.at(i)); - } - if ((part.triangleIndices.size() % 3) != 0){ - qCDebug(modelformat) << "Error in extractFBXGeometry part.triangleIndices.size() is not divisible by three "; - } - } + createMeshTangents(extracted.mesh, generateTangents); + for (auto& blendShape : extracted.mesh.blendshapes) { + createBlendShapeTangents(extracted.mesh, generateTangents, blendShape); } // find the clusters with which the mesh is associated diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 843a874a62..34d63b098a 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -33,6 +33,15 @@ class QIODevice; class FBXNode; +#define FBX_PACK_NORMALS 1 + +#if FBX_PACK_NORMALS +using NormalType = glm::uint32; +#define FBX_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2 +#else +using NormalType = glm::vec3; +#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ +#endif /// Reads FBX geometry from the supplied model and mapping data. /// \exception QString if an error occurs in parsing @@ -114,6 +123,8 @@ public: QHash meshes; static void buildModelMesh(FBXMesh& extractedMesh, const QString& url); + static glm::vec3 normalizeDirForPacking(const glm::vec3& dir); + FBXTexture getTexture(const QString& textureID); QHash _textureNames; diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index f76435c216..309c421052 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -37,6 +37,20 @@ #include +#include +#include + +using vec2h = glm::tvec2; + +#define FBX_PACK_COLORS 1 + +#if FBX_PACK_COLORS +using ColorType = glm::uint32; +#define FBX_COLOR_ELEMENT gpu::Element::COLOR_RGBA_32 +#else +using ColorType = glm::vec3; +#define FBX_COLOR_ELEMENT gpu::Element::VEC3F_XYZ +#endif class Vertex { public: @@ -225,7 +239,7 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn foreach (const FBXNode& subdata, child.children) { if (subdata.name == "Colors") { data.colors = createVec4VectorRGBA(getDoubleVector(subdata), data.averageColor); - } else if (subdata.name == "ColorsIndex") { + } else if (subdata.name == "ColorsIndex" || subdata.name == "ColorIndex") { data.colorIndices = getIntVector(subdata); } else if (subdata.name == "MappingInformationType" && subdata.properties.at(0) == BY_VERTICE) { @@ -543,6 +557,14 @@ ExtractedMesh FBXReader::extractMesh(const FBXNode& object, unsigned int& meshIn return data.extracted; } +glm::vec3 FBXReader::normalizeDirForPacking(const glm::vec3& dir) { + auto maxCoord = glm::max(fabsf(dir.x), glm::max(fabsf(dir.y), fabsf(dir.z))); + if (maxCoord > 1e-6f) { + return dir / maxCoord; + } + return dir; +} + void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*"); @@ -571,37 +593,116 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { gpu::BufferView vbv(vb, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); mesh->setVertexBuffer(vbv); + if (!fbxMesh.normals.empty() && fbxMesh.tangents.empty()) { + // Fill with a dummy value to force tangents to be present if there are normals + fbxMesh.tangents.reserve(fbxMesh.normals.size()); + std::fill_n(std::back_inserter(fbxMesh.tangents), fbxMesh.normals.size(), Vectors::UNIT_X); + } + // Same thing with blend shapes + for (auto& blendShape : fbxMesh.blendshapes) { + if (!blendShape.normals.empty() && blendShape.tangents.empty()) { + // Fill with a dummy value to force tangents to be present if there are normals + blendShape.tangents.reserve(blendShape.normals.size()); + std::fill_n(std::back_inserter(fbxMesh.tangents), blendShape.normals.size(), Vectors::UNIT_X); + } + } + // evaluate all attribute channels sizes - int normalsSize = fbxMesh.normals.size() * sizeof(glm::vec3); - int tangentsSize = fbxMesh.tangents.size() * sizeof(glm::vec3); - int colorsSize = fbxMesh.colors.size() * sizeof(glm::vec3); - int texCoordsSize = fbxMesh.texCoords.size() * sizeof(glm::vec2); - int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(glm::vec2); + const int normalsSize = fbxMesh.normals.size() * sizeof(NormalType); + const int tangentsSize = fbxMesh.tangents.size() * sizeof(NormalType); + // If there are normals then there should be tangents + assert(normalsSize == tangentsSize); + const auto normalsAndTangentsSize = normalsSize + tangentsSize; + const int normalsAndTangentsStride = 2 * sizeof(NormalType); + const int colorsSize = fbxMesh.colors.size() * sizeof(ColorType); + // Texture coordinates are stored in 2 half floats + const int texCoordsSize = fbxMesh.texCoords.size() * sizeof(vec2h); + const int texCoords1Size = fbxMesh.texCoords1.size() * sizeof(vec2h); int clusterIndicesSize = fbxMesh.clusterIndices.size() * sizeof(uint8_t); if (fbxMesh.clusters.size() > UINT8_MAX) { // we need 16 bits instead of just 8 for clusterIndices clusterIndicesSize *= 2; } - int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint16_t); - int normalsOffset = 0; - int tangentsOffset = normalsOffset + normalsSize; - int colorsOffset = tangentsOffset + tangentsSize; - int texCoordsOffset = colorsOffset + colorsSize; - int texCoords1Offset = texCoordsOffset + texCoordsSize; - int clusterIndicesOffset = texCoords1Offset + texCoords1Size; - int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; - int totalAttributeSize = clusterWeightsOffset + clusterWeightsSize; + const int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint16_t); + + // Normals and tangents are interleaved + const int normalsOffset = 0; + const int tangentsOffset = normalsOffset + sizeof(NormalType); + const int colorsOffset = normalsOffset + normalsSize + tangentsSize; + const int texCoordsOffset = colorsOffset + colorsSize; + const int texCoords1Offset = texCoordsOffset + texCoordsSize; + const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; + const int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; + const int totalAttributeSize = clusterWeightsOffset + clusterWeightsSize; // Copy all attribute data in a single attribute buffer auto attribBuffer = std::make_shared(); attribBuffer->resize(totalAttributeSize); - attribBuffer->setSubData(normalsOffset, normalsSize, (gpu::Byte*) fbxMesh.normals.constData()); - attribBuffer->setSubData(tangentsOffset, tangentsSize, (gpu::Byte*) fbxMesh.tangents.constData()); - attribBuffer->setSubData(colorsOffset, colorsSize, (gpu::Byte*) fbxMesh.colors.constData()); - attribBuffer->setSubData(texCoordsOffset, texCoordsSize, (gpu::Byte*) fbxMesh.texCoords.constData()); - attribBuffer->setSubData(texCoords1Offset, texCoords1Size, (gpu::Byte*) fbxMesh.texCoords1.constData()); + + // Interleave normals and tangents + if (normalsSize > 0) { + std::vector normalsAndTangents; + + normalsAndTangents.reserve(fbxMesh.normals.size() + fbxMesh.tangents.size()); + for (auto normalIt = fbxMesh.normals.constBegin(), tangentIt = fbxMesh.tangents.constBegin(); + normalIt != fbxMesh.normals.constEnd(); + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + const auto normal = normalizeDirForPacking(*normalIt); + const auto tangent = normalizeDirForPacking(*tangentIt); + const auto packedNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); + const auto packedTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); +#else + const auto packedNormal = *normalIt; + const auto packedTangent = *tangentIt; +#endif + normalsAndTangents.push_back(packedNormal); + normalsAndTangents.push_back(packedTangent); + } + attribBuffer->setSubData(normalsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); + } + + if (colorsSize > 0) { +#if FBX_PACK_COLORS + std::vector colors; + + colors.reserve(fbxMesh.colors.size()); + for (const auto& color : fbxMesh.colors) { + colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); + } + attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); +#else + attribBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) fbxMesh.colors.constData()); +#endif + } + + if (texCoordsSize > 0) { + QVector texCoordData; + texCoordData.reserve(fbxMesh.texCoords.size()); + for (auto& texCoordVec2f : fbxMesh.texCoords) { + vec2h texCoordVec2h; + + texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); + texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); + texCoordData.push_back(texCoordVec2h); + } + attribBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); + } + + if (texCoords1Size > 0) { + QVector texCoordData; + texCoordData.reserve(fbxMesh.texCoords1.size()); + for (auto& texCoordVec2f : fbxMesh.texCoords1) { + vec2h texCoordVec2h; + + texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); + texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); + texCoordData.push_back(texCoordVec2h); + } + attribBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); + } if (fbxMesh.clusters.size() < UINT8_MAX) { // yay! we can fit the clusterIndices within 8-bits @@ -612,40 +713,37 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) { assert(fbxMesh.clusterIndices[i] <= UINT8_MAX); clusterIndices[i] = (uint8_t)(fbxMesh.clusterIndices[i]); } - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (gpu::Byte*) clusterIndices.constData()); + attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); } else { - attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (gpu::Byte*) fbxMesh.clusterIndices.constData()); + attribBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) fbxMesh.clusterIndices.constData()); } - attribBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (gpu::Byte*) fbxMesh.clusterWeights.constData()); + attribBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) fbxMesh.clusterWeights.constData()); if (normalsSize) { mesh->addAttribute(gpu::Stream::NORMAL, - model::BufferView(attribBuffer, normalsOffset, normalsSize, - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ))); - } - if (tangentsSize) { + model::BufferView(attribBuffer, normalsOffset, normalsAndTangentsSize, + normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); mesh->addAttribute(gpu::Stream::TANGENT, - model::BufferView(attribBuffer, tangentsOffset, tangentsSize, - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ))); + model::BufferView(attribBuffer, tangentsOffset, normalsAndTangentsSize, + normalsAndTangentsStride, FBX_NORMAL_ELEMENT)); } if (colorsSize) { mesh->addAttribute(gpu::Stream::COLOR, - model::BufferView(attribBuffer, colorsOffset, colorsSize, - gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB))); + model::BufferView(attribBuffer, colorsOffset, colorsSize, FBX_COLOR_ELEMENT)); } if (texCoordsSize) { mesh->addAttribute(gpu::Stream::TEXCOORD, - model::BufferView( attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV))); + model::BufferView( attribBuffer, texCoordsOffset, texCoordsSize, + gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); } if (texCoords1Size) { mesh->addAttribute( gpu::Stream::TEXCOORD1, model::BufferView(attribBuffer, texCoords1Offset, texCoords1Size, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV))); + gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); } else if (texCoordsSize) { mesh->addAttribute(gpu::Stream::TEXCOORD1, - model::BufferView(attribBuffer, texCoordsOffset, texCoordsSize, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV))); + model::BufferView(attribBuffer, texCoordsOffset, texCoordsSize, + gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV))); } if (clusterIndicesSize) { diff --git a/libraries/gpu-gl/src/gpu/gl/GLShared.h b/libraries/gpu-gl/src/gpu/gl/GLShared.h index d3ad7c028b..6c2948a736 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLShared.h +++ b/libraries/gpu-gl/src/gpu/gl/GLShared.h @@ -110,7 +110,8 @@ static const GLenum ELEMENT_TYPE_TO_GL[gpu::NUM_TYPES] = { GL_UNSIGNED_SHORT, GL_BYTE, GL_UNSIGNED_BYTE, - GL_UNSIGNED_BYTE + GL_UNSIGNED_BYTE, + GL_INT_2_10_10_10_REV, }; bool checkGLError(const char* name = nullptr); diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index 528a2b524b..7b3db4e4fe 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -218,6 +218,7 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::NINT8: result = GL_RGBA8_SNORM; break; + case gpu::NINT2_10_10_10: case gpu::NUINT32: case gpu::NINT32: case gpu::COMPRESSED: @@ -502,6 +503,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } case gpu::COMPRESSED: case gpu::NUINT2: + case gpu::NINT2_10_10_10: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -553,6 +555,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E } case gpu::COMPRESSED: case gpu::NUINT2: + case gpu::NINT2_10_10_10: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -671,6 +674,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; case gpu::NUINT32: case gpu::NINT32: + case gpu::NINT2_10_10_10: case gpu::COMPRESSED: case gpu::NUM_TYPES: // quiet compiler Q_UNREACHABLE(); diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index 3b153097cf..c4b6b39723 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -38,6 +38,7 @@ const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; const Element Element::VEC3F_XYZ{ VEC3, FLOAT, XYZ }; const Element Element::VEC4F_XYZW{ VEC4, FLOAT, XYZW }; +const Element Element::VEC4F_NORMALIZED_XYZ10W2{ VEC4, NINT2_10_10_10, XYZW }; const Element Element::INDEX_UINT16 { SCALAR, UINT16, INDEX }; const Element Element::INDEX_INT32 { SCALAR, INT32, INDEX }; const Element Element::PART_DRAWCALL{ VEC4, UINT32, PART }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index 9d5d2fc49d..17102d0415 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -39,6 +39,7 @@ enum Type : uint8_t { NINT8, NUINT8, NUINT2, + NINT2_10_10_10, COMPRESSED, @@ -65,6 +66,7 @@ static const int TYPE_SIZE[NUM_TYPES] = { 2, 1, 1, + 4, 1 }; @@ -86,6 +88,7 @@ static const bool TYPE_IS_INTEGER[NUM_TYPES] = { false, false, false, + false, false, }; @@ -326,6 +329,7 @@ public: static const Element VEC2F_XY; static const Element VEC3F_XYZ; static const Element VEC4F_XYZW; + static const Element VEC4F_NORMALIZED_XYZ10W2; static const Element INDEX_UINT16; static const Element INDEX_INT32; static const Element PART_DRAWCALL; diff --git a/libraries/gpu/src/gpu/Stream.cpp b/libraries/gpu/src/gpu/Stream.cpp index 427af1e78d..caa1ecbc06 100644 --- a/libraries/gpu/src/gpu/Stream.cpp +++ b/libraries/gpu/src/gpu/Stream.cpp @@ -92,6 +92,15 @@ bool Stream::Format::setAttribute(Slot slot, Slot channel, Frequency frequency) return true; } +Stream::Attribute Stream::Format::getAttribute(Slot slot) const { + auto attribIt = _attributes.find(slot); + if (attribIt != _attributes.end()) { + return attribIt->second; + } else { + return Attribute(); + } +} + void BufferStream::addBuffer(const BufferPointer& buffer, Offset offset, Offset stride) { _buffers.push_back(buffer); _offsets.push_back(offset); diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index 336e34ecb4..5562980a91 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -112,6 +112,7 @@ public: bool setAttribute(Slot slot, Slot channel, Frequency frequency = PER_VERTEX); bool hasAttribute(Slot slot) const { return (_attributes.find(slot) != _attributes.end()); } + Attribute getAttribute(Slot slot) const; const std::string& getKey() const { return _key; } diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index 6c2471f680..69c35c4a20 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -3,6 +3,7 @@ // libraries/midi/src // // Created by Burt Sloane +// Modified by Bruce Brown // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -14,30 +15,45 @@ #include - #if defined Q_OS_WIN32 #include "Windows.h" #endif - #if defined Q_OS_WIN32 const int MIDI_BYTE_MASK = 0x0FF; +const int MIDI_NIBBLE_MASK = 0x00F; +const int MIDI_PITCH_BEND_MASK = 0x3F80; +const int MIDI_SHIFT_STATUS = 4; const int MIDI_SHIFT_NOTE = 8; const int MIDI_SHIFT_VELOCITY = 16; +const int MIDI_SHIFT_PITCH_BEND = 9; +// Status Decode +const int MIDI_NOTE_OFF = 0x8; +const int MIDI_NOTE_ON = 0x9; +const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa; +const int MIDI_PROGRAM_CHANGE = 0xc; +const int MIDI_CHANNEL_PRESSURE = 0xd; +const int MIDI_PITCH_BEND_CHANGE = 0xe; +const int MIDI_SYSTEM_MESSAGE = 0xf; #endif -const int MIDI_STATUS_MASK = 0x0F0; -const int MIDI_NOTE_OFF = 0x080; -const int MIDI_NOTE_ON = 0x090; -const int MIDI_CONTROL_CHANGE = 0x0b0; + +const int MIDI_CONTROL_CHANGE = 0xb; const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b; - -static Midi* instance = NULL; // communicate this to non-class callbacks +static Midi* instance = NULL; // communicate this to non-class callbacks static bool thruModeEnabled = false; +static bool broadcastEnabled = false; +static bool typeNoteOffEnabled = true; +static bool typeNoteOnEnabled = true; +static bool typePolyKeyPressureEnabled = false; +static bool typeControlChangeEnabled = true; +static bool typeProgramChangeEnabled = true; +static bool typeChanPressureEnabled = false; +static bool typePitchBendEnabled = true; +static bool typeSystemMessageEnabled = false; -std::vector Midi::midiinexclude; -std::vector Midi::midioutexclude; - +std::vector Midi::midiInExclude; +std::vector Midi::midiOutExclude; #if defined Q_OS_WIN32 @@ -47,7 +63,6 @@ std::vector Midi::midioutexclude; std::vector midihin; std::vector midihout; - void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { switch (wMsg) { case MIM_OPEN: @@ -58,23 +73,64 @@ void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD if (midihin[i] == hMidiIn) { midihin[i] = NULL; instance->allNotesOff(); + instance->midiHardwareChange(); } } break; case MIM_DATA: { - int status = MIDI_BYTE_MASK & dwParam1; - int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE); - int vel = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY); - if (thruModeEnabled) { - instance->sendNote(status, note, vel); // relay the note on to all other midi devices + int device = -1; + for (int i = 0; i < midihin.size(); i++) { + if (midihin[i] == hMidiIn) { + device = i; + } } - instance->noteReceived(status, note, vel); // notify the javascript + int raw = dwParam1; + int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1; + int status = MIDI_BYTE_MASK & dwParam1; + int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS); + int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE); + int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY); + int bend = 0; + int program = 0; + if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) { + return; + } + if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) { + return; + } + if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) { + return; + } + if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) { + return; + } + if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) { + program = note; + note = 0; + } + if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) { + velocity = note; + note = 0; + } + if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) { + bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) | + (MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192; + channel = 0; // Weird values on different instruments + note = 0; + velocity = 0; + } + if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) { + return; + } + if (thruModeEnabled) { + instance->sendNote(status, note, velocity); // relay the message on to all other midi devices. + } + instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript break; } } } - void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { switch (wMsg) { case MOM_OPEN: @@ -85,21 +141,45 @@ void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_P if (midihout[i] == hmo) { midihout[i] = NULL; instance->allNotesOff(); + instance->midiHardwareChange(); } } break; } } - -void Midi::sendNote(int status, int note, int vel) { - for (int i = 0; i < midihout.size(); i++) { - if (midihout[i] != NULL) { - midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (vel << MIDI_SHIFT_VELOCITY)); +void Midi::sendRawMessage(int device, int raw) { + if (broadcastEnabled) { + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], raw); + } } + } else { + midiOutShortMsg(midihout[device], raw); } } +void Midi::sendMessage(int device, int channel, int type, int note, int velocity) { + int message = (channel - 1) | (type << MIDI_SHIFT_STATUS); + if (broadcastEnabled) { + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); + } + } + } else { + midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY)); + } +} + +void Midi::sendNote(int status, int note, int velocity) { + for (int i = 0; i < midihout.size(); i++) { + if (midihout[i] != NULL) { + midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY)); + } + } +} void Midi::MidiSetup() { midihin.clear(); @@ -110,8 +190,8 @@ void Midi::MidiSetup() { midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); bool found = false; - for (int j = 0; j < midiinexclude.size(); j++) { - if (midiinexclude[j].toStdString().compare(incaps.szPname) == 0) { + for (int j = 0; j < midiInExclude.size(); j++) { + if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) { found = true; break; } @@ -122,7 +202,6 @@ void Midi::MidiSetup() { midiInStart(tmphin); midihin.push_back(tmphin); } - } MIDIOUTCAPS outcaps; @@ -130,8 +209,8 @@ void Midi::MidiSetup() { midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); bool found = false; - for (int j = 0; j < midioutexclude.size(); j++) { - if (midioutexclude[j].toStdString().compare(outcaps.szPname) == 0) { + for (int j = 0; j < midiOutExclude.size(); j++) { + if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) { found = true; break; } @@ -164,7 +243,13 @@ void Midi::MidiCleanup() { midihout.clear(); } #else -void Midi::sendNote(int status, int note, int vel) { +void Midi::sendRawMessage(int device, int raw) { +} + +void Midi::sendNote(int status, int note, int velocity) { +} + +void Midi::sendMessage(int device, int channel, int type, int note, int velocity){ } void Midi::MidiSetup() { @@ -176,26 +261,30 @@ void Midi::MidiCleanup() { } #endif -void Midi::noteReceived(int status, int note, int velocity) { - if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) && - ((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON) && - ((status & MIDI_STATUS_MASK) != MIDI_CONTROL_CHANGE)) { - return; // NOTE: only sending note-on, note-off, and control-change to Javascript - } - +void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) { QVariantMap eventData; + eventData["device"] = device; + eventData["raw"] = raw; + eventData["channel"] = channel; eventData["status"] = status; + eventData["type"] = type; eventData["note"] = note; eventData["velocity"] = velocity; - emit midiNote(eventData); + eventData["bend"] = bend; + eventData["program"] = program; + emit midiNote(eventData);// Legacy + emit midiMessage(eventData); } +void Midi::midiHardwareChange() { + emit midiReset(); +} // Midi::Midi() { instance = this; #if defined Q_OS_WIN32 - midioutexclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing + midiOutExclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing (Lags) #endif MidiSetup(); } @@ -203,10 +292,18 @@ Midi::Midi() { Midi::~Midi() { } +void Midi::sendRawDword(int device, int raw) { + sendRawMessage(device, raw); +} + void Midi::playMidiNote(int status, int note, int velocity) { sendNote(status, note, velocity); } +void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) { + sendMessage(device, channel, type, note, velocity); +} + void Midi::allNotesOff() { sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off } @@ -219,6 +316,7 @@ void Midi::resetDevices() { void Midi::USBchanged() { instance->MidiCleanup(); instance->MidiSetup(); + instance->midiHardwareChange(); } // @@ -245,16 +343,16 @@ QStringList Midi::listMidiDevices(bool output) { void Midi::unblockMidiDevice(QString name, bool output) { if (output) { - for (unsigned long i = 0; i < midioutexclude.size(); i++) { - if (midioutexclude[i].toStdString().compare(name.toStdString()) == 0) { - midioutexclude.erase(midioutexclude.begin() + i); + for (unsigned long i = 0; i < midiOutExclude.size(); i++) { + if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) { + midiOutExclude.erase(midiOutExclude.begin() + i); break; } } } else { - for (unsigned long i = 0; i < midiinexclude.size(); i++) { - if (midiinexclude[i].toStdString().compare(name.toStdString()) == 0) { - midiinexclude.erase(midiinexclude.begin() + i); + for (unsigned long i = 0; i < midiInExclude.size(); i++) { + if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) { + midiInExclude.erase(midiInExclude.begin() + i); break; } } @@ -264,9 +362,9 @@ void Midi::unblockMidiDevice(QString name, bool output) { void Midi::blockMidiDevice(QString name, bool output) { unblockMidiDevice(name, output); // make sure it's only in there once if (output) { - midioutexclude.push_back(name); + midiOutExclude.push_back(name); } else { - midiinexclude.push_back(name); + midiInExclude.push_back(name); } } @@ -274,3 +372,38 @@ void Midi::thruModeEnable(bool enable) { thruModeEnabled = enable; } +void Midi::broadcastEnable(bool enable) { + broadcastEnabled = enable; +} + +void Midi::typeNoteOffEnable(bool enable) { + typeNoteOffEnabled = enable; +} + +void Midi::typeNoteOnEnable(bool enable) { + typeNoteOnEnabled = enable; +} + +void Midi::typePolyKeyPressureEnable(bool enable) { + typePolyKeyPressureEnabled = enable; +} + +void Midi::typeControlChangeEnable(bool enable) { + typeControlChangeEnabled = enable; +} + +void Midi::typeProgramChangeEnable(bool enable) { + typeProgramChangeEnabled = enable; +} + +void Midi::typeChanPressureEnable(bool enable) { + typeChanPressureEnabled = enable; +} + +void Midi::typePitchBendEnable(bool enable) { + typePitchBendEnabled = enable; +} + +void Midi::typeSystemMessageEnable(bool enable) { + typeSystemMessageEnabled = enable; +} diff --git a/libraries/midi/src/Midi.h b/libraries/midi/src/Midi.h index 013ec056e3..f7940bbe5d 100644 --- a/libraries/midi/src/Midi.h +++ b/libraries/midi/src/Midi.h @@ -3,6 +3,7 @@ // libraries/midi/src // // Created by Burt Sloane +// Modified by Bruce Brown // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -24,13 +25,16 @@ class Midi : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - void noteReceived(int status, int note, int velocity); // relay a note to Javascript - void sendNote(int status, int note, int vel); // relay a note to MIDI outputs + void midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program); // relay a note to Javascript + void midiHardwareChange(); // relay hardware change to Javascript + void sendRawMessage(int device, int raw); // relay midi message to MIDI outputs + void sendNote(int status, int note, int velocity); // relay a note to MIDI outputs + void sendMessage(int device, int channel, int type, int note, int velocity); // relay a message to MIDI outputs static void USBchanged(); private: - static std::vector midiinexclude; - static std::vector midioutexclude; + static std::vector midiInExclude; + static std::vector midiOutExclude; private: void MidiSetup(); @@ -38,31 +42,63 @@ private: signals: void midiNote(QVariantMap eventData); + void midiMessage(QVariantMap eventData); + void midiReset(); -public slots: -/// play a note on all connected devices -/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc -/// @param {int} note: midi note number -/// @param {int} velocity: note velocity (0 means noteoff) -Q_INVOKABLE void playMidiNote(int status, int note, int velocity); + public slots: + // Send Raw Midi Packet to all connected devices + Q_INVOKABLE void sendRawDword(int device, int raw); + /// Send Raw Midi message to selected device + /// @param {int} device: device number + /// @param {int} raw: raw midi message (DWORD) -/// turn off all notes on all connected devices -Q_INVOKABLE void allNotesOff(); + // Send Midi Message to all connected devices + Q_INVOKABLE void sendMidiMessage(int device, int channel, int type, int note, int velocity); + /// Send midi message to selected device/devices + /// @param {int} device: device number + /// @param {int} channel: channel number + /// @param {int} type: 0x8 is noteoff, 0x9 is noteon (if velocity=0, noteoff), etc + /// @param {int} note: midi note number + /// @param {int} velocity: note velocity (0 means noteoff) -/// clean up and re-discover attached devices -Q_INVOKABLE void resetDevices(); + // Send Midi Message to all connected devices + Q_INVOKABLE void playMidiNote(int status, int note, int velocity); + /// play a note on all connected devices + /// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc + /// @param {int} note: midi note number + /// @param {int} velocity: note velocity (0 means noteoff) -/// ask for a list of inputs/outputs -Q_INVOKABLE QStringList listMidiDevices(bool output); + /// turn off all notes on all connected devices + Q_INVOKABLE void allNotesOff(); -/// block an input/output by name -Q_INVOKABLE void blockMidiDevice(QString name, bool output); + /// clean up and re-discover attached devices + Q_INVOKABLE void resetDevices(); -/// unblock an input/output by name -Q_INVOKABLE void unblockMidiDevice(QString name, bool output); + /// ask for a list of inputs/outputs + Q_INVOKABLE QStringList listMidiDevices(bool output); + + /// block an input/output by name + Q_INVOKABLE void blockMidiDevice(QString name, bool output); + + /// unblock an input/output by name + Q_INVOKABLE void unblockMidiDevice(QString name, bool output); + + /// repeat all incoming notes to all outputs (default disabled) + Q_INVOKABLE void thruModeEnable(bool enable); + + /// broadcast on all unblocked devices + Q_INVOKABLE void broadcastEnable(bool enable); + + /// filter by event types + Q_INVOKABLE void typeNoteOffEnable(bool enable); + Q_INVOKABLE void typeNoteOnEnable(bool enable); + Q_INVOKABLE void typePolyKeyPressureEnable(bool enable); + Q_INVOKABLE void typeControlChangeEnable(bool enable); + Q_INVOKABLE void typeProgramChangeEnable(bool enable); + Q_INVOKABLE void typeChanPressureEnable(bool enable); + Q_INVOKABLE void typePitchBendEnable(bool enable); + Q_INVOKABLE void typeSystemMessageEnable(bool enable); -/// repeat all incoming notes to all outputs (default disabled) -Q_INVOKABLE void thruModeEnable(bool enable); public: Midi(); diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index 5627746c43..210b4ae8f3 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -11,6 +11,8 @@ #include "Geometry.h" +#include + using namespace model; Mesh::Mesh() : @@ -72,12 +74,14 @@ void Mesh::evalVertexStream() { int channelNum = 0; if (hasVertexData()) { - _vertexStream.addBuffer(_vertexBuffer._buffer, _vertexBuffer._offset, _vertexFormat->getChannelStride(channelNum)); + auto stride = glm::max(_vertexFormat->getChannelStride(channelNum), _vertexBuffer._stride); + _vertexStream.addBuffer(_vertexBuffer._buffer, _vertexBuffer._offset, stride); channelNum++; } for (auto attrib : _attributeBuffers) { BufferView& view = attrib.second; - _vertexStream.addBuffer(view._buffer, view._offset, _vertexFormat->getChannelStride(channelNum)); + auto stride = glm::max(_vertexFormat->getChannelStride(channelNum), view._stride); + _vertexStream.addBuffer(view._buffer, view._offset, stride); channelNum++; } } @@ -137,13 +141,15 @@ model::MeshPointer Mesh::map(std::function vertexFunc, std::function colorFunc, std::function normalFunc, std::function indexFunc) const { + const auto vertexFormat = getVertexFormat(); + // vertex data const gpu::BufferView& vertexBufferView = getVertexBuffer(); gpu::BufferView::Index numVertices = (gpu::BufferView::Index)getNumVertices(); gpu::Resource::Size vertexSize = numVertices * sizeof(glm::vec3); - unsigned char* resultVertexData = new unsigned char[vertexSize]; - unsigned char* vertexDataCursor = resultVertexData; + std::unique_ptr resultVertexData{ new unsigned char[vertexSize] }; + unsigned char* vertexDataCursor = resultVertexData.get(); for (gpu::BufferView::Index i = 0; i < numVertices; i++) { glm::vec3 pos = vertexFunc(vertexBufferView.get(i)); @@ -157,13 +163,24 @@ model::MeshPointer Mesh::map(std::function vertexFunc, gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); gpu::Resource::Size colorSize = numColors * sizeof(glm::vec3); - unsigned char* resultColorData = new unsigned char[colorSize]; - unsigned char* colorDataCursor = resultColorData; + std::unique_ptr resultColorData{ new unsigned char[colorSize] }; + unsigned char* colorDataCursor = resultColorData.get(); + auto colorAttribute = vertexFormat->getAttribute(attributeTypeColor); - for (gpu::BufferView::Index i = 0; i < numColors; i++) { - glm::vec3 color = colorFunc(colorsBufferView.get(i)); - memcpy(colorDataCursor, &color, sizeof(color)); - colorDataCursor += sizeof(color); + if (colorAttribute._element == gpu::Element::VEC3F_XYZ) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + glm::vec3 color = colorFunc(colorsBufferView.get(i)); + memcpy(colorDataCursor, &color, sizeof(color)); + colorDataCursor += sizeof(color); + } + } else if (colorAttribute._element == gpu::Element::COLOR_RGBA_32) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + auto rawColor = colorsBufferView.get(i); + auto color = glm::vec3(glm::unpackUnorm4x8(rawColor)); + color = colorFunc(color); + memcpy(colorDataCursor, &color, sizeof(color)); + colorDataCursor += sizeof(color); + } } // normal data @@ -171,22 +188,34 @@ model::MeshPointer Mesh::map(std::function vertexFunc, const gpu::BufferView& normalsBufferView = getAttributeBuffer(attributeTypeNormal); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); gpu::Resource::Size normalSize = numNormals * sizeof(glm::vec3); - unsigned char* resultNormalData = new unsigned char[normalSize]; - unsigned char* normalDataCursor = resultNormalData; + std::unique_ptr resultNormalData{ new unsigned char[normalSize] }; + unsigned char* normalDataCursor = resultNormalData.get(); + auto normalAttribute = vertexFormat->getAttribute(attributeTypeNormal); - for (gpu::BufferView::Index i = 0; i < numNormals; i++) { - glm::vec3 normal = normalFunc(normalsBufferView.get(i)); - memcpy(normalDataCursor, &normal, sizeof(normal)); - normalDataCursor += sizeof(normal); + if (normalAttribute._element == gpu::Element::VEC3F_XYZ) { + for (gpu::BufferView::Index i = 0; i < numNormals; i++) { + glm::vec3 normal = normalFunc(normalsBufferView.get(i)); + memcpy(normalDataCursor, &normal, sizeof(normal)); + normalDataCursor += sizeof(normal); + } + } else if (normalAttribute._element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + auto packedNormal = normalsBufferView.get(i); + auto normal = glm::vec3(glm::unpackSnorm3x10_1x2(packedNormal)); + normal = normalFunc(normal); + memcpy(normalDataCursor, &normal, sizeof(normal)); + normalDataCursor += sizeof(normal); + } } + // TODO -- other attributes // face data const gpu::BufferView& indexBufferView = getIndexBuffer(); gpu::BufferView::Index numIndexes = (gpu::BufferView::Index)getNumIndices(); gpu::Resource::Size indexSize = numIndexes * sizeof(uint32_t); - unsigned char* resultIndexData = new unsigned char[indexSize]; - unsigned char* indexDataCursor = resultIndexData; + std::unique_ptr resultIndexData{ new unsigned char[indexSize] }; + unsigned char* indexDataCursor = resultIndexData.get(); for (gpu::BufferView::Index i = 0; i < numIndexes; i++) { uint32_t index = indexFunc(indexBufferView.get(i)); @@ -197,25 +226,25 @@ model::MeshPointer Mesh::map(std::function vertexFunc, model::MeshPointer result(new model::Mesh()); gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* resultVertexBuffer = new gpu::Buffer(vertexSize, resultVertexData); + gpu::Buffer* resultVertexBuffer = new gpu::Buffer(vertexSize, resultVertexData.get()); gpu::BufferPointer resultVertexBufferPointer(resultVertexBuffer); gpu::BufferView resultVertexBufferView(resultVertexBufferPointer, vertexElement); result->setVertexBuffer(resultVertexBufferView); gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* resultColorsBuffer = new gpu::Buffer(colorSize, resultColorData); + gpu::Buffer* resultColorsBuffer = new gpu::Buffer(colorSize, resultColorData.get()); gpu::BufferPointer resultColorsBufferPointer(resultColorsBuffer); gpu::BufferView resultColorsBufferView(resultColorsBufferPointer, colorElement); result->addAttribute(attributeTypeColor, resultColorsBufferView); gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* resultNormalsBuffer = new gpu::Buffer(normalSize, resultNormalData); + gpu::Buffer* resultNormalsBuffer = new gpu::Buffer(normalSize, resultNormalData.get()); gpu::BufferPointer resultNormalsBufferPointer(resultNormalsBuffer); gpu::BufferView resultNormalsBufferView(resultNormalsBufferPointer, normalElement); result->addAttribute(attributeTypeNormal, resultNormalsBufferView); gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); - gpu::Buffer* resultIndexesBuffer = new gpu::Buffer(indexSize, resultIndexData); + gpu::Buffer* resultIndexesBuffer = new gpu::Buffer(indexSize, resultIndexData.get()); gpu::BufferPointer resultIndexesBufferPointer(resultIndexesBuffer); gpu::BufferView resultIndexesBufferView(resultIndexesBufferPointer, indexElement); result->setIndexBuffer(resultIndexesBufferView); @@ -239,6 +268,8 @@ void Mesh::forEach(std::function vertexFunc, std::function colorFunc, std::function normalFunc, std::function indexFunc) { + const auto vertexFormat = getVertexFormat(); + // vertex data const gpu::BufferView& vertexBufferView = getVertexBuffer(); gpu::BufferView::Index numVertices = (gpu::BufferView::Index)getNumVertices(); @@ -250,17 +281,36 @@ void Mesh::forEach(std::function vertexFunc, int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h const gpu::BufferView& colorsBufferView = getAttributeBuffer(attributeTypeColor); gpu::BufferView::Index numColors = (gpu::BufferView::Index)colorsBufferView.getNumElements(); - for (gpu::BufferView::Index i = 0; i < numColors; i++) { - colorFunc(colorsBufferView.get(i)); + auto colorAttribute = vertexFormat->getAttribute(attributeTypeColor); + if (colorAttribute._element == gpu::Element::VEC3F_XYZ) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + colorFunc(colorsBufferView.get(i)); + } + } else if (colorAttribute._element == gpu::Element::COLOR_RGBA_32) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + auto rawColor = colorsBufferView.get(i); + auto color = glm::unpackUnorm4x8(rawColor); + colorFunc(color); + } } // normal data int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h const gpu::BufferView& normalsBufferView = getAttributeBuffer(attributeTypeNormal); gpu::BufferView::Index numNormals = (gpu::BufferView::Index)normalsBufferView.getNumElements(); - for (gpu::BufferView::Index i = 0; i < numNormals; i++) { - normalFunc(normalsBufferView.get(i)); + auto normalAttribute = vertexFormat->getAttribute(attributeTypeNormal); + if (normalAttribute._element == gpu::Element::VEC3F_XYZ) { + for (gpu::BufferView::Index i = 0; i < numNormals; i++) { + normalFunc(normalsBufferView.get(i)); + } + } else if (normalAttribute._element == gpu::Element::VEC4F_NORMALIZED_XYZ10W2) { + for (gpu::BufferView::Index i = 0; i < numColors; i++) { + auto packedNormal = normalsBufferView.get(i); + auto normal = glm::unpackSnorm3x10_1x2(packedNormal); + normalFunc(normal); + } } + // TODO -- other attributes // face data diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 7f89b47197..42ef2f627d 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -84,6 +84,46 @@ public: void softReset(); + /**jsdoc + *

The reasons that you may be refused connection to a domain are defined by numeric values:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
ReasonValueDescription
Unknown0Some unknown reason.
ProtocolMismatch1The communications protocols of the domain and your Interface are not the same.
LoginError2You could not be logged into the domain.
NotAuthorized3You are not authorized to connect to the domain.
TooManyUsers4The domain already has its maximum number of users.
+ * @typedef Window.ConnectionRefusedReason + */ enum class ConnectionRefusedReason : uint8_t { Unknown, ProtocolMismatch, diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 44f86baf40..db670c0a88 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::OwnershipChallengeFix); + return static_cast(EntityVersion::ZoneStageRemoved); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b2f22e2a1f..7eafbbccf5 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -202,6 +202,8 @@ enum class EntityVersion : PacketVersion { HazeEffect, StaticCertJsonVersionOne, OwnershipChallengeFix, + ZoneLightInheritModes, + ZoneStageRemoved }; enum class EntityScriptCallMethodVersion : PacketVersion { diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 09eb134124..ef4e98798f 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -28,7 +28,6 @@ #include #include -#include #include #include #include @@ -258,7 +257,6 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, rgbColor& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::quat& result) { int bytes = unpackOrientationQuatFromBytes(dataBytes, result); return bytes; } static int unpackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int unpackDataFromBytes(const unsigned char* dataBytes, BackgroundMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, QString& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result); static int unpackDataFromBytes(const unsigned char* dataBytes, xColor& result); diff --git a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp index 607adee2b7..3d213840dd 100644 --- a/libraries/render-utils/src/CauterizedMeshPartPayload.cpp +++ b/libraries/render-utils/src/CauterizedMeshPartPayload.cpp @@ -46,10 +46,7 @@ void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::S } batch.setModelTransform(_cauterizedTransform); } else { - if (_clusterBuffer) { - batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer); - } - batch.setModelTransform(_transform); + ModelMeshPartPayload::bindTransform(batch, locations, renderMode); } } diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 151fb7990d..81a33f17e3 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -763,7 +763,8 @@ void DefaultLightingSetup::run(const RenderContextPointer& renderContext) { _defaultLight = lp; // Add the global light to the light stage (for later shadow rendering) - _defaultLightID = lightStage->addLight(lp); + // Set this light to be the default + _defaultLightID = lightStage->addLight(lp, true); lightStage->addShadow(_defaultLightID); } diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index eedb9053c7..d6ac7fd2e2 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -37,14 +37,13 @@ enum LightClusterGridShader_MapSlot { }; enum LightClusterGridShader_BufferSlot { - LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT = 0, - DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT =1, - CAMERA_CORRECTION_BUFFER_SLOT = 2, + DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT = 0, + CAMERA_CORRECTION_BUFFER_SLOT = 1, LIGHT_GPU_SLOT = render::ShapePipeline::Slot::LIGHT, - LIGHT_INDEX_GPU_SLOT = 5, - - LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT = 6, - LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT = 7, + LIGHT_INDEX_GPU_SLOT = 7, + LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT = 8, + LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT = 9, + LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT = 10, }; FrustumGrid::FrustumGrid(const FrustumGrid& source) : diff --git a/libraries/render-utils/src/LightPayload.h b/libraries/render-utils/src/LightPayload.h index 0cf8f8e2de..b55373c9a8 100644 --- a/libraries/render-utils/src/LightPayload.h +++ b/libraries/render-utils/src/LightPayload.h @@ -67,7 +67,6 @@ public: NetworkTexturePointer _ambientTexture; QString _ambientTextureURL; bool _pendingAmbientTexture { false }; - bool _validAmbientTextureURL { false }; protected: model::LightPointer _light; diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index e568554452..42b161ba74 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -28,6 +28,37 @@ static const auto MAX_BIAS = 0.006f; const LightStage::Index LightStage::INVALID_INDEX { render::indexed_container::INVALID_INDEX }; LightStage::LightStage() { + // Add off lights + const LightPointer ambientOffLight { std::make_shared() }; + ambientOffLight->setAmbientIntensity(0.0f); + ambientOffLight->setColor(model::Vec3(0.0)); + ambientOffLight->setIntensity(0.0f); + ambientOffLight->setType(model::Light::Type::AMBIENT); + _ambientOffLightId = addLight(ambientOffLight); + + const LightPointer pointOffLight { std::make_shared() }; + pointOffLight->setAmbientIntensity(0.0f); + pointOffLight->setColor(model::Vec3(0.0)); + pointOffLight->setIntensity(0.0f); + pointOffLight->setType(model::Light::Type::POINT); + _pointOffLightId = addLight(pointOffLight); + + const LightPointer spotOffLight { std::make_shared() }; + spotOffLight->setAmbientIntensity(0.0f); + spotOffLight->setColor(model::Vec3(0.0)); + spotOffLight->setIntensity(0.0f); + spotOffLight->setType(model::Light::Type::SPOT); + _spotOffLightId = addLight(spotOffLight); + + const LightPointer sunOffLight { std::make_shared() }; + sunOffLight->setAmbientIntensity(0.0f); + sunOffLight->setColor(model::Vec3(0.0)); + sunOffLight->setIntensity(0.0f); + sunOffLight->setType(model::Light::Type::SUN); + _sunOffLightId = addLight(sunOffLight); + + // Set default light to the off ambient light (until changed) + _defaultLightId = _ambientOffLightId; } LightStage::Shadow::Schema::Schema() { @@ -266,10 +297,12 @@ LightStage::Index LightStage::findLight(const LightPointer& light) const { } } -LightStage::Index LightStage::addLight(const LightPointer& light) { +LightStage::Index LightStage::addLight(const LightPointer& light, const bool shouldSetAsDefault) { + Index lightId; + auto found = _lightMap.find(light); if (found == _lightMap.end()) { - auto lightId = _lights.newElement(light); + lightId = _lights.newElement(light); // Avoid failing to allocate a light, just pass if (lightId != INVALID_INDEX) { @@ -286,10 +319,15 @@ LightStage::Index LightStage::addLight(const LightPointer& light) { updateLightArrayBuffer(lightId); } - return lightId; } else { - return (*found).second; + lightId = (*found).second; } + + if (shouldSetAsDefault) { + _defaultLightId = lightId; + } + + return lightId; } LightStage::Index LightStage::addShadow(Index lightIndex, float maxDistance, unsigned int cascadeCount) { @@ -321,7 +359,7 @@ LightStage::LightPointer LightStage::removeLight(Index index) { } LightStage::LightPointer LightStage::getCurrentKeyLight() const { - Index keyLightId{ 0 }; + Index keyLightId{ _defaultLightId }; if (!_currentFrame._sunLights.empty()) { keyLightId = _currentFrame._sunLights.front(); } @@ -329,7 +367,7 @@ LightStage::LightPointer LightStage::getCurrentKeyLight() const { } LightStage::LightPointer LightStage::getCurrentAmbientLight() const { - Index keyLightId{ 0 }; + Index keyLightId { _defaultLightId }; if (!_currentFrame._ambientLights.empty()) { keyLightId = _currentFrame._ambientLights.front(); } @@ -337,7 +375,7 @@ LightStage::LightPointer LightStage::getCurrentAmbientLight() const { } LightStage::ShadowPointer LightStage::getCurrentKeyShadow() const { - Index keyLightId{ 0 }; + Index keyLightId { _defaultLightId }; if (!_currentFrame._sunLights.empty()) { keyLightId = _currentFrame._sunLights.front(); } @@ -347,7 +385,7 @@ LightStage::ShadowPointer LightStage::getCurrentKeyShadow() const { } LightStage::LightAndShadow LightStage::getCurrentKeyLightAndShadow() const { - Index keyLightId{ 0 }; + Index keyLightId { _defaultLightId }; if (!_currentFrame._sunLights.empty()) { keyLightId = _currentFrame._sunLights.front(); } diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 508e67ec17..3dcae550f7 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -118,7 +118,9 @@ public: using Shadows = render::indexed_container::IndexedPointerVector; Index findLight(const LightPointer& light) const; - Index addLight(const LightPointer& light); + Index addLight(const LightPointer& light, const bool shouldSetAsDefault = false); + + Index getDefaultLight() { return _defaultLightId; } Index addShadow(Index lightIndex, float maxDistance = 20.0f, unsigned int cascadeCount = 1U); @@ -185,6 +187,11 @@ public: Frame _currentFrame; + Index getAmbientOffLight() { return _ambientOffLightId; } + Index getPointOffLight() { return _pointOffLightId; } + Index getSpotOffLight() { return _spotOffLightId; } + Index getSunOffLight() { return _sunOffLightId; } + protected: struct Desc { @@ -199,6 +206,13 @@ protected: Descs _descs; LightMap _lightMap; + // define off lights + Index _ambientOffLightId; + Index _pointOffLightId; + Index _spotOffLightId; + Index _sunOffLightId; + + Index _defaultLightId; }; using LightStagePointer = std::shared_ptr; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 6b7ac87426..d6ab2ff416 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -488,23 +488,15 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const { } void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) { - if (!_isBlendShaped) { - batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); - batch.setInputFormat((_drawMesh->getVertexFormat())); - batch.setInputStream(0, _drawMesh->getVertexStream()); + batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); + batch.setInputFormat((_drawMesh->getVertexFormat())); + if (_isBlendShaped && _blendedVertexBuffer) { + batch.setInputBuffer(0, _blendedVertexBuffer, 0, sizeof(glm::vec3)); + // Stride is 2*sizeof(glm::vec3) because normal and tangents are interleaved + batch.setInputBuffer(1, _blendedVertexBuffer, _drawMesh->getNumVertices() * sizeof(glm::vec3), 2 * sizeof(NormalType)); + batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2)); } else { - batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); - batch.setInputFormat((_drawMesh->getVertexFormat())); - - if (_blendedVertexBuffer) { - batch.setInputBuffer(0, _blendedVertexBuffer, 0, sizeof(glm::vec3)); - batch.setInputBuffer(1, _blendedVertexBuffer, _drawMesh->getNumVertices() * sizeof(glm::vec3), sizeof(glm::vec3)); - batch.setInputStream(2, _drawMesh->getVertexStream().makeRangedStream(2)); - } else { - batch.setIndexBuffer(gpu::UINT32, (_drawMesh->getIndexBuffer()._buffer), 0); - batch.setInputFormat((_drawMesh->getVertexFormat())); - batch.setInputStream(0, _drawMesh->getVertexStream()); - } + batch.setInputStream(0, _drawMesh->getVertexStream()); } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index ff61eea5c4..9c1c579341 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -24,9 +24,13 @@ #include #include #include +#include + #include #include +#include + #include "AbstractViewStateInterface.h" #include "MeshPartPayload.h" @@ -319,6 +323,34 @@ void Model::reset() { } } +#if FBX_PACK_NORMALS +static void packNormalAndTangent(glm::vec3 normal, glm::vec3 tangent, glm::uint32& packedNormal, glm::uint32& packedTangent) { + auto absNormal = glm::abs(normal); + auto absTangent = glm::abs(tangent); + normal /= glm::max(1e-6f, glm::max(glm::max(absNormal.x, absNormal.y), absNormal.z)); + tangent /= glm::max(1e-6f, glm::max(glm::max(absTangent.x, absTangent.y), absTangent.z)); + normal = glm::clamp(normal, -1.0f, 1.0f); + tangent = glm::clamp(tangent, -1.0f, 1.0f); + normal *= 511.0f; + tangent *= 511.0f; + normal = glm::round(normal); + tangent = glm::round(tangent); + + glm::detail::i10i10i10i2 normalStruct; + glm::detail::i10i10i10i2 tangentStruct; + normalStruct.data.x = int(normal.x); + normalStruct.data.y = int(normal.y); + normalStruct.data.z = int(normal.z); + normalStruct.data.w = 0; + tangentStruct.data.x = int(tangent.x); + tangentStruct.data.y = int(tangent.y); + tangentStruct.data.z = int(tangent.z); + tangentStruct.data.w = 0; + packedNormal = normalStruct.pack; + packedTangent = tangentStruct.pack; +} +#endif + bool Model::updateGeometry() { bool needFullUpdate = false; @@ -344,10 +376,28 @@ bool Model::updateGeometry() { // TODO? make _blendedVertexBuffers a map instead of vector and only add for meshes with blendshapes? auto buffer = std::make_shared(); if (!mesh.blendshapes.isEmpty()) { - buffer->resize((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); - buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.vertices.constData()); + std::vector normalsAndTangents; + normalsAndTangents.reserve(mesh.normals.size() + mesh.tangents.size()); + + for (auto normalIt = mesh.normals.begin(), tangentIt = mesh.tangents.begin(); + normalIt != mesh.normals.end(); + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); +#else + const auto finalNormal = *normalIt; + const auto finalTangent = *tangentIt; +#endif + normalsAndTangents.push_back(finalNormal); + normalsAndTangents.push_back(finalTangent); + } + + buffer->resize(mesh.vertices.size() * (sizeof(glm::vec3) + 2 * sizeof(NormalType))); + buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (const gpu::Byte*) mesh.vertices.constData()); buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) mesh.normals.constData()); + mesh.normals.size() * 2 * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); } _blendedVertexBuffers.push_back(buffer); } @@ -1015,7 +1065,7 @@ Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointe void Blender::run() { DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); - QVector vertices, normals; + QVector vertices, normals, tangents; if (_model) { int offset = 0; foreach (const FBXMesh& mesh, _meshes) { @@ -1024,8 +1074,10 @@ void Blender::run() { } vertices += mesh.vertices; normals += mesh.normals; + tangents += mesh.tangents; glm::vec3* meshVertices = vertices.data() + offset; glm::vec3* meshNormals = normals.data() + offset; + glm::vec3* meshTangents = tangents.data() + offset; offset += mesh.vertices.size(); const float NORMAL_COEFFICIENT_SCALE = 0.01f; for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { @@ -1040,6 +1092,7 @@ void Blender::run() { int index = blendshape.indices.at(j); meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; meshNormals[index] += blendshape.normals.at(j) * normalCoefficient; + meshTangents[index] += blendshape.tangents.at(j) * normalCoefficient; } } } @@ -1048,7 +1101,7 @@ void Blender::run() { QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector&, vertices), - Q_ARG(const QVector&, normals)); + Q_ARG(const QVector&, normals), Q_ARG(const QVector&, tangents)); } void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) { @@ -1238,7 +1291,7 @@ bool Model::maybeStartBlender() { } void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals) { + const QVector& vertices, const QVector& normals, const QVector& tangents) { auto geometryRef = geometry.lock(); if (!geometryRef || _renderGeometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) { return; @@ -1246,6 +1299,7 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo _appliedBlendNumber = blendNumber; const FBXGeometry& fbxGeometry = getFBXGeometry(); int index = 0; + std::vector normalsAndTangents; for (int i = 0; i < fbxGeometry.meshes.size(); i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); if (mesh.blendshapes.isEmpty()) { @@ -1253,11 +1307,67 @@ void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geo } gpu::BufferPointer& buffer = _blendedVertexBuffers[i]; - buffer->setSubData(0, mesh.vertices.size() * sizeof(glm::vec3), (gpu::Byte*) vertices.constData() + index*sizeof(glm::vec3)); - buffer->setSubData(mesh.vertices.size() * sizeof(glm::vec3), - mesh.normals.size() * sizeof(glm::vec3), (gpu::Byte*) normals.constData() + index*sizeof(glm::vec3)); + const auto vertexCount = mesh.vertices.size(); + const auto verticesSize = vertexCount * sizeof(glm::vec3); + const auto offset = index * sizeof(glm::vec3); - index += mesh.vertices.size(); + normalsAndTangents.clear(); + normalsAndTangents.resize(normals.size()+tangents.size()); + assert(normalsAndTangents.size() == 2 * vertexCount); + + // Interleave normals and tangents +#if 0 + // Sequential version for debugging + auto normalsRange = std::make_pair(normals.begin() + index, normals.begin() + index + vertexCount); + auto tangentsRange = std::make_pair(tangents.begin() + index, tangents.begin() + index + vertexCount); + auto normalsAndTangentsIt = normalsAndTangents.begin(); + + for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; + normalIt != normalsRange.second; + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); +#else + const auto finalNormal = *normalIt; + const auto finalTangent = *tangentIt; +#endif + *normalsAndTangentsIt = finalNormal; + ++normalsAndTangentsIt; + *normalsAndTangentsIt = finalTangent; + ++normalsAndTangentsIt; + } +#else + // Parallel version for performance + tbb::parallel_for(tbb::blocked_range(index, index+vertexCount), [&](const tbb::blocked_range& range) { + auto normalsRange = std::make_pair(normals.begin() + range.begin(), normals.begin() + range.end()); + auto tangentsRange = std::make_pair(tangents.begin() + range.begin(), tangents.begin() + range.end()); + auto normalsAndTangentsIt = normalsAndTangents.begin() + (range.begin()-index)*2; + + for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; + normalIt != normalsRange.second; + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); +#else + const auto finalNormal = *normalIt; + const auto finalTangent = *tangentIt; +#endif + *normalsAndTangentsIt = finalNormal; + ++normalsAndTangentsIt; + *normalsAndTangentsIt = finalTangent; + ++normalsAndTangentsIt; + } + }); +#endif + + buffer->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + offset); + buffer->setSubData(verticesSize, 2 * vertexCount * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); + + index += vertexCount; } } @@ -1424,10 +1534,11 @@ void ModelBlender::noteRequiresBlend(ModelPointer model) { } } -void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, - const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals) { +void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, + const QVector& vertices, const QVector& normals, + const QVector& tangents) { if (model) { - model->setBlendedVertices(blendNumber, geometry, vertices, normals); + model->setBlendedVertices(blendNumber, geometry, vertices, normals, tangents); } _pendingBlenders--; { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index d13a76847a..623f869666 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -82,7 +82,7 @@ public: /// Sets the URL of the model to render. // Should only be called from the model's rendering thread to avoid access violations of changed geometry. - Q_INVOKABLE void setURL(const QUrl& url); + Q_INVOKABLE virtual void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } // new Scene/Engine rendering support @@ -118,7 +118,7 @@ public: /// Sets blended vertices computed in a separate thread. void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals); + const QVector& vertices, const QVector& normals, const QVector& tangents); bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isGeometryLoaded(); } bool isAddedToScene() const { return _addedToScene; } @@ -140,7 +140,7 @@ public: const Geometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } - Q_INVOKABLE void setTextures(const QVariantMap& textures); + Q_INVOKABLE virtual void setTextures(const QVariantMap& textures); /// Provided as a convenience, will crash if !isLoaded() // And so that getGeometry() isn't chained everywhere @@ -478,7 +478,7 @@ public: public slots: void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, - const QVector& vertices, const QVector& normals); + const QVector& vertices, const QVector& normals, const QVector& tangents); private: using Mutex = std::mutex; diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 8494688f92..c0d01c2eaf 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -68,10 +68,11 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) auto lightStage = context->_scene->getStage(); assert(lightStage); - lightStage->_currentFrame.pushSunLight(0); - lightStage->_currentFrame.pushAmbientLight(0); - hazeStage->_currentFrame.pushHaze(0); + lightStage->_currentFrame.pushSunLight(lightStage->getDefaultLight()); + lightStage->_currentFrame.pushAmbientLight(lightStage->getDefaultLight()); + backgroundStage->_currentFrame.pushBackground(0); + hazeStage->_currentFrame.pushHaze(0); } const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() { diff --git a/libraries/script-engine/src/ModelScriptingInterface.cpp b/libraries/script-engine/src/ModelScriptingInterface.cpp index 3234a079ec..e58afeaba8 100644 --- a/libraries/script-engine/src/ModelScriptingInterface.cpp +++ b/libraries/script-engine/src/ModelScriptingInterface.cpp @@ -60,20 +60,20 @@ QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { // alloc the resulting mesh gpu::Resource::Size combinedVertexSize = totalVertexCount * sizeof(glm::vec3); - unsigned char* combinedVertexData = new unsigned char[combinedVertexSize]; - unsigned char* combinedVertexDataCursor = combinedVertexData; + std::unique_ptr combinedVertexData{ new unsigned char[combinedVertexSize] }; + unsigned char* combinedVertexDataCursor = combinedVertexData.get(); gpu::Resource::Size combinedColorSize = totalColorCount * sizeof(glm::vec3); - unsigned char* combinedColorData = new unsigned char[combinedColorSize]; - unsigned char* combinedColorDataCursor = combinedColorData; + std::unique_ptr combinedColorData{ new unsigned char[combinedColorSize] }; + unsigned char* combinedColorDataCursor = combinedColorData.get(); gpu::Resource::Size combinedNormalSize = totalNormalCount * sizeof(glm::vec3); - unsigned char* combinedNormalData = new unsigned char[combinedNormalSize]; - unsigned char* combinedNormalDataCursor = combinedNormalData; + std::unique_ptr combinedNormalData{ new unsigned char[combinedNormalSize] }; + unsigned char* combinedNormalDataCursor = combinedNormalData.get(); gpu::Resource::Size combinedIndexSize = totalIndexCount * sizeof(uint32_t); - unsigned char* combinedIndexData = new unsigned char[combinedIndexSize]; - unsigned char* combinedIndexDataCursor = combinedIndexData; + std::unique_ptr combinedIndexData{ new unsigned char[combinedIndexSize] }; + unsigned char* combinedIndexDataCursor = combinedIndexData.get(); uint32_t indexStartOffset { 0 }; @@ -105,27 +105,27 @@ QScriptValue ModelScriptingInterface::appendMeshes(MeshProxyList in) { model::MeshPointer result(new model::Mesh()); gpu::Element vertexElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData); + gpu::Buffer* combinedVertexBuffer = new gpu::Buffer(combinedVertexSize, combinedVertexData.get()); gpu::BufferPointer combinedVertexBufferPointer(combinedVertexBuffer); gpu::BufferView combinedVertexBufferView(combinedVertexBufferPointer, vertexElement); result->setVertexBuffer(combinedVertexBufferView); int attributeTypeColor = gpu::Stream::InputSlot::COLOR; // libraries/gpu/src/gpu/Stream.h gpu::Element colorElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData); + gpu::Buffer* combinedColorsBuffer = new gpu::Buffer(combinedColorSize, combinedColorData.get()); gpu::BufferPointer combinedColorsBufferPointer(combinedColorsBuffer); gpu::BufferView combinedColorsBufferView(combinedColorsBufferPointer, colorElement); result->addAttribute(attributeTypeColor, combinedColorsBufferView); int attributeTypeNormal = gpu::Stream::InputSlot::NORMAL; // libraries/gpu/src/gpu/Stream.h gpu::Element normalElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData); + gpu::Buffer* combinedNormalsBuffer = new gpu::Buffer(combinedNormalSize, combinedNormalData.get()); gpu::BufferPointer combinedNormalsBufferPointer(combinedNormalsBuffer); gpu::BufferView combinedNormalsBufferView(combinedNormalsBufferPointer, normalElement); result->addAttribute(attributeTypeNormal, combinedNormalsBufferView); gpu::Element indexElement = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW); - gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData); + gpu::Buffer* combinedIndexesBuffer = new gpu::Buffer(combinedIndexSize, combinedIndexData.get()); gpu::BufferPointer combinedIndexesBufferPointer(combinedIndexesBuffer); gpu::BufferView combinedIndexesBufferView(combinedIndexesBufferPointer, indexElement); result->setIndexBuffer(combinedIndexesBufferView); @@ -152,9 +152,10 @@ QScriptValue ModelScriptingInterface::transformMesh(glm::mat4 transform, MeshPro return QScriptValue(false); } + const auto inverseTransposeTransform = glm::inverse(glm::transpose(transform)); model::MeshPointer result = mesh->map([&](glm::vec3 position){ return glm::vec3(transform * glm::vec4(position, 1.0f)); }, [&](glm::vec3 color){ return color; }, - [&](glm::vec3 normal){ return glm::vec3(transform * glm::vec4(normal, 0.0f)); }, + [&](glm::vec3 normal){ return glm::vec3(inverseTransposeTransform * glm::vec4(normal, 0.0f)); }, [&](uint32_t index){ return index; }); MeshProxy* resultProxy = new SimpleMeshProxy(result); return meshToScriptValue(_modelScriptEngine, resultProxy); diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index f2b0105be1..0f807b08cf 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -31,6 +31,7 @@ class ScriptEngines : public QObject, public Dependency { Q_PROPERTY(ScriptsModel* scriptsModel READ scriptsModel CONSTANT) Q_PROPERTY(ScriptsModelFilter* scriptsModelFilter READ scriptsModelFilter CONSTANT) + Q_PROPERTY(QString debugScriptUrl READ getDebugScriptUrl WRITE setDebugScriptUrl) public: using ScriptInitializer = std::function; @@ -41,6 +42,9 @@ public: void loadScripts(); void saveScripts(); + QString getDebugScriptUrl() { return _debugScriptUrl; }; + void setDebugScriptUrl(const QString& url) { _debugScriptUrl = url; }; + QString getScriptsLocation() const; void loadDefaultScripts(); void setScriptsLocation(const QString& scriptsLocation); @@ -117,6 +121,7 @@ protected: std::atomic _isStopped { false }; std::atomic _isReloading { false }; bool _defaultScriptsLocationOverridden { false }; + QString _debugScriptUrl; }; QUrl normalizeScriptURL(const QUrl& rawScriptURL); diff --git a/libraries/shared/src/BackgroundMode.h b/libraries/shared/src/BackgroundMode.h deleted file mode 100644 index 0e0d684e62..0000000000 --- a/libraries/shared/src/BackgroundMode.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// BackgroundMode.h -// libraries/physics/src -// -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_BackgroundMode_h -#define hifi_BackgroundMode_h - -enum BackgroundMode { - BACKGROUND_MODE_INHERIT, - BACKGROUND_MODE_SKYBOX, - - BACKGROUND_MODE_ITEM_COUNT, -}; - - -#endif // hifi_BackgroundMode_h - diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 3d06a4b223..4632c8ab76 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -262,7 +262,7 @@ controller::Pose ovrControllerPoseToHandPose( pose.translation = toGlm(handPose.ThePose.Position); pose.translation += rotation * translationOffset; pose.rotation = rotation * rotationOffset; - pose.angularVelocity = toGlm(handPose.AngularVelocity); + pose.angularVelocity = rotation * toGlm(handPose.AngularVelocity); pose.velocity = toGlm(handPose.LinearVelocity); pose.valid = true; return pose; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 89d4c75ae4..71755e3abb 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -29,7 +29,8 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/notifications.js", "system/dialTone.js", "system/firstPersonHMD.js", - "system/tablet-ui/tabletUI.js" + "system/tablet-ui/tabletUI.js", + "system/emote.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/controllerScripts.js" diff --git a/scripts/developer/sunModel.js b/scripts/developer/sunModel.js new file mode 100644 index 0000000000..f36f0fadc2 --- /dev/null +++ b/scripts/developer/sunModel.js @@ -0,0 +1,325 @@ +// +// sunModel.js +// scripts/developer +// +// Created by Nissim Hadar on 2017/12/27. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Sun angle is based on the NOAA model - see https://www.esrl.noaa.gov/gmd/grad/solcalc/ +// +(function() { + // Utility functions for trig. calculations + function toRadians(angle_degs) { + return angle_degs * (Math.PI / 180); + } + function toDegrees(angle_rads) { + return angle_rads * (180.0 / Math.PI); + } + + // Code to check if Daylight Savings is active + Date.prototype.stdTimezoneOffset = function() { + var fy = this.getFullYear(); + if (!Date.prototype.stdTimezoneOffset.cache.hasOwnProperty(fy)) { + var maxOffset = new Date(fy, 0, 1).getTimezoneOffset(); + var monthsTestOrder = [6, 7, 5, 8, 4, 9, 3, 10, 2, 11, 1]; + + for(var mi = 0;mi < 12; ++mi) { + var offset = new Date(fy, monthsTestOrder[mi], 1).getTimezoneOffset(); + if (offset != maxOffset) { + maxOffset = Math.max(maxOffset, offset); + break; + } + } + Date.prototype.stdTimezoneOffset.cache[fy] = maxOffset; + } + return Date.prototype.stdTimezoneOffset.cache[fy]; + }; + + // Cache the result for per year stdTimezoneOffset so that you don't need to recalculate it when testing multiple dates in + // the same year. + Date.prototype.stdTimezoneOffset.cache = {}; + + Date.prototype.isDST = function() { + return this.getTimezoneOffset() < this.stdTimezoneOffset(); + }; + + // The Julian Date is the number of days (fractional) that have elapsed since Jan 1st, 4713 BC + // See https://quasar.as.utexas.edu/BillInfo/JulianDatesG.html + function getJulianDay(dateTime) { + var month = dateTime.getMonth() + 1; + var day = dateTime.getDate() + 1; + var year = dateTime.getFullYear(); + + if (month <= 2) { + year -= 1; + month += 12; + } + + var A = Math.floor(year / 100); + var B = 2 - A + Math.floor(A / 4); + return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5; + } + + function getMinutes(dateTime) { + var hours = dateTime.getHours(); + var minutes = dateTime.getMinutes(); + var seconds = dateTime.getSeconds(); + + if (Date.prototype.isDST()) { + hour -= 1; + } + + return hours * 60 + minutes + seconds / 60.0; + } + + function calcGeomMeanAnomalySun(t) { + var M = 357.52911 + t * (35999.05029 - 0.0001537 * t); + return M; // in degrees + } + + function calcSunEqOfCenter(t) { + var m = calcGeomMeanAnomalySun(t); + var mrad = toRadians(m); + var sinm = Math.sin(mrad); + var sin2m = Math.sin(mrad + mrad); + var sin3m = Math.sin(mrad + mrad + mrad); + var C = sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289; + return C; // in degrees + } + + function calcGeomMeanLongSun(t) { + var L0 = 280.46646 + t * (36000.76983 + t*(0.0003032)) + while(L0 > 360.0) { + L0 -= 360.0 + } + while(L0 < 0.0) { + L0 += 360.0 + } + return L0 // in degrees + } + + function calcSunTrueLong(t) { + var l0 = calcGeomMeanLongSun(t); + var c = calcSunEqOfCenter(t); + var O = l0 + c; + return O; // in degrees + } + + function calcSunApparentLong(t) { + var o = calcSunTrueLong(t); + var omega = 125.04 - 1934.136 * t; + var lambda = o - 0.00569 - 0.00478 * Math.sin(toRadians(omega)); + return lambda; // in degrees + } + + function calcMeanObliquityOfEcliptic(t) { + var seconds = 21.448 - t * (46.8150 + t * (0.00059 - t * (0.001813))); + var e0 = 23.0 + (26.0 + (seconds / 60.0)) / 60.0; + return e0; // in degrees + } + + function calcObliquityCorrection(t) { + var e0 = calcMeanObliquityOfEcliptic(t); + var omega = 125.04 - 1934.136 * t; + var e = e0 + 0.00256 * Math.cos(toRadians(omega)); + return e; // in degrees + } + + function calcSunDeclination(t) { + var e = calcObliquityCorrection(t); + var lambda = calcSunApparentLong(t); + + var sint = Math.sin(toRadians(e)) * Math.sin(toRadians(lambda)); + var theta = toDegrees(Math.asin(sint)); + return theta; // in degrees + } + + function calcEccentricityEarthOrbit(t) { + var e = 0.016708634 - t * (0.000042037 + 0.0000001267 * t); + return e; // unitless + } + + function calcEquationOfTime(t) { + // Converts from "mean" solar day (i.e. days that are exactly 24 hours) + // to apparent solar day (as observed) + // This is essentially the east-west component of the analemma. + // + // This is caused by 2 effects: the obliquity of the ecliptic, the eccentricity of earth's orbit + var epsilon = calcObliquityCorrection(t); + var l0 = calcGeomMeanLongSun(t); + var e = calcEccentricityEarthOrbit(t); + var m = calcGeomMeanAnomalySun(t); + + var y = Math.tan(toRadians(epsilon) / 2.0); + y *= y; + + var sin2l0 = Math.sin(2.0 * toRadians(l0)); + var sinm = Math.sin(toRadians(m)); + var cos2l0 = Math.cos(2.0 * toRadians(l0)); + var sin4l0 = Math.sin(4.0 * toRadians(l0)); + var sin2m = Math.sin(2.0 * toRadians(m)); + + var Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - + 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m; + + return toDegrees(Etime) * 4.0; // in minutes of time + } + + function calcSunTrueAnomaly(t) { + var m = calcGeomMeanAnomalySun(t); + var c = calcSunEqOfCenter(t); + var v = m + c; + return v; // in degrees + } + + function calcSunRadVector(t) { + var v = calcSunTrueAnomaly(t); + var e = calcEccentricityEarthOrbit(t); + var R = (1.000001018 * (1 - e * e)) / (1 + e * Math.cos(toRadians(v))); + return R; // in AUs + } + + function parseJSON(json) { + try { + return JSON.parse(json); + } catch (e) { + return undefined; + } + } + + var COMPUTATION_CYCLE = 5000; // Run every 5 seconds + this.preload = function(entityID) { // We don't have the entityID before the preload + // Define user data + var userDataProperties = { + "userData": "{ \"latitude\": 47.0, \"longitude\": 122.0 }" + }; + Entities.editEntity(entityID, userDataProperties); + + Script.setInterval( + function() { + // Read back user data + var userData = Entities.getEntityProperties(entityID, 'userData').userData; + var data = parseJSON(userData); + + var latitude_degs = data.latitude; + var longitude_degs = data.longitude; + + // These are used a lot + var latitude = toRadians(latitude_degs); + var longitude = toRadians(longitude_degs); + + var dateTime = new Date(); + + var julianDay = getJulianDay(dateTime); + var localTimeMinutes = getMinutes(dateTime); + var timeZone = -dateTime.getTimezoneOffset() / 60; + var totalTime = julianDay + localTimeMinutes/1440.0 - timeZone / 24.0; + + // J2000.0 is the epoch starting Jan 1st, 2000 (noon), expressed as a Julian day + var J2000 = 2451545.0 + + // Number of years that have passed since J2000.0 + var julianDayModified = (J2000 - 2451545.0)/36525.0; + + var eqTime = calcEquationOfTime(julianDayModified) + var theta_rads = toRadians(calcSunDeclination(julianDayModified)); + var solarTimeFix = eqTime + 4.0 * longitude_degs - 60.0 * timeZone; + var earthRadVec = calcSunRadVector(julianDayModified); + + var trueSolarTime = localTimeMinutes + solarTimeFix; + while (trueSolarTime > 1440) { + trueSolarTime -= 1440; + } + + var hourAngle = trueSolarTime / 4.0 - 180.0; + if (hourAngle < -180.0) { + hourAngle += 360.0; + } + var hourAngleRadians = toRadians(hourAngle); + + var csz = Math.sin(latitude) * Math.sin(theta_rads) + + Math.cos(latitude) * Math.cos(theta_rads) * Math.cos(hourAngleRadians); + csz = Math.min(1.0, Math.max(-1.0, csz)); + + var zenith_degs = toDegrees(Math.acos(csz)); + var azDenom = ( Math.cos(latitude) * Math.sin(toRadians(zenith_degs))); + if (Math.abs(azDenom) > 0.001) { + azRad = (( Math.sin(latitude) * Math.cos(toRadians(zenith_degs)) ) - Math.sin(theta_rads)) / azDenom; + if (Math.abs(azRad) > 1.0) { + if (azRad < 0.0) { + azRad = -1.0; + } else { + azRad = 1.0; + } + } + var solarAzimuth_degs = 180.0 - toDegrees(Math.acos(azRad)) + if (hourAngle > 0.0) { + solarAzimuth_degs = -solarAzimuth_degs; + } + } else { + if (latitude_degs > 0.0) { + solarAzimuth_degs = 180.0; + } else { + solarAzimuth_degs = 0.0; + } + } + if (solarAzimuth_degs < 0.0) { + solarAzimuth_degs += 360.0; + } + + // Atmospheric Refraction correction + var exoatmElevation = 90.0 - zenith_degs; + if (exoatmElevation > 85.0) { + var refractionCorrection = 0.0; + } else { + var te = Math.tan(toRadians(exoatmElevation)); + if (exoatmElevation > 5.0) { + var refractionCorrection = 58.1 / te - 0.07 / (te * te * te) + 0.000086 / (te * te * te * te * te); + } else if (exoatmElevation > -0.575) { + var refractionCorrection = + 1735.0 + exoatmElevation * + (-518.2 + exoatmElevation * (103.4 + exoatmElevation * (-12.79 + exoatmElevation * 0.711))); + } else { + var refractionCorrection = -20.774 / te; + } + refractionCorrection = refractionCorrection / 3600.0; + } + + var solarZenith = zenith_degs - refractionCorrection; + var solarAltitude_degs = 90.0 - solarZenith; // aka solar elevation + + // Convert to XYZ + var solarAltitude = toRadians(solarAltitude_degs); + var solarAzimuth = toRadians(solarAzimuth_degs); + + var xPos = Math.cos(solarAltitude) * Math.sin(solarAzimuth); + var zPos = Math.cos(solarAltitude) * Math.cos(solarAzimuth); + var yPos = -Math.sin(solarAltitude); + + // Compute intensity, modelling the atmosphere as a spherical shell + // The optical air mass ratio at zenith is 1.0, and around 38.0 at the horizon + // The ratio is limited between 1 and 38 + var EARTH_RADIUS_KM = 6371.0; + var ATMOSPHERE_THICKNESS_KM = 9.0; + var r = EARTH_RADIUS_KM / ATMOSPHERE_THICKNESS_KM; + + var opticalAirMassRatio = Math.sqrt(r * r * csz * csz + 2 * r + 1) - r * csz; + opticalAirMassRatio = Math.min(38.0, Math.max(1.0, opticalAirMassRatio)); + + Entities.editEntity( + entityID, { + keyLight : { + direction: { x: xPos, y: yPos, z: zPos }, + intensity: 1.0 / opticalAirMassRatio + } + } + ); + }, + + COMPUTATION_CYCLE + ); + }; +}); \ No newline at end of file diff --git a/scripts/developer/utilities/tools/currentAPI.js b/scripts/developer/utilities/tools/currentAPI.js index cb9f152794..175b84b8d9 100644 --- a/scripts/developer/utilities/tools/currentAPI.js +++ b/scripts/developer/utilities/tools/currentAPI.js @@ -9,34 +9,173 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var array = []; -function listKeys(string, object) { - if (string === "listKeys" || string === "array" || string === "buffer" || string === "i") { - return; + +(function(){ + var array = []; + var mainKeys = Object.keys(this); + var qml = Script.resourcesPath() + '/qml/CurrentAPI.qml'; + var needsUpdate = false; + var updateTime = 20; + var updateData = []; + var deltaTime = 0; + var maxUpdatingMethods = 20; + var scriptPath = ""; + + if (ScriptDiscoveryService.debugScriptUrl != "") { + Script.include(ScriptDiscoveryService.debugScriptUrl); } - if (typeof(object) !== "object" || object === null) { - array.push(string + " " + typeof(object)); - return; - } - - var keys = Object.keys(object); - for (var i = 0; i < keys.length; ++i) { - if (string === "") { - listKeys(keys[i], object[keys[i]]); - } else if (keys[i] !== "parent") { - listKeys(string + "." + keys[i], object[keys[i]]); + var window = new OverlayWindow({ + title: 'API Debugger', + source: qml, + width: 1200, + height: 500 + }); + + window.closed.connect(function () { + Script.stop(); + }); + + function addMainKeys(){ + var keys = Object.keys(this); + for (var i = 0; i < keys.length; i++) { + array.push({member:keys[i] , type: "class"}); } } -} -listKeys("", this); -array.sort(); + function memberHasValue(member, type) { + if (type === "function()") { + if (member.indexOf(".has") < 0 && member.indexOf(".is") < 0 && member.indexOf(".get") < 0) { + return false; + } + if (member.indexOf("indow") < 0) { + return true; + } + } else if (type === "boolean" || type === "string" || type === "number" || type === "user") { + return true; + } + return false; + } -var buffer = "\n======= JS API list ======="; -for (var i = 0; i < array.length; ++i) { - buffer += "\n" + array[i]; -} -buffer += "\n========= API END =========\n"; + function listKeys(string, object) { + if (string === "listKeys" || string === "array" || string === "buffer" || string === "i") { + return; + } + + if (typeof(object) !== "object" || object === null) { + var type = typeof(object); + if (type === "function") { + chain = string.split("("); + if (chain.length > 1) { + string = chain[0]; + type = "function(" + chain[1]; + } else { + type = "function()"; + } + } + var value = ""; + var hasValue = false; + if (memberHasValue(string, type)){ + var evalstring = type === "function()" ? string+"()" : string; + try{ + value = "" + eval(evalstring); + hasValue = true; + } catch(e) { + value = "Error evaluating"; + } + } + array.push({member:string , type: type, value: value, hasValue: hasValue}); + return; + } + + var keys = Object.keys(object); + for (var i = 0; i < keys.length; ++i) { + if (string === "") { + listKeys(keys[i], object[keys[i]]); + } else if (keys[i] !== "parent") { + listKeys(string + "." + keys[i], object[keys[i]]); + } + } + } + + function findMethods(addon, object, addkeys) { + array = []; + var string = addkeys ? "" : addon+"."; + listKeys(string, object); + if (addkeys) { + addMainKeys(); + } + array.sort(function(a, b){ + if(a.member < b.member) return -1; + if(a.member > b.member) return 1; + return 0; + }); + }; + + findMethods("", this, true); + window.sendToQml({type:"methods", data:array}); + + window.fromQml.connect(function(message){ + if (message.type == "refreshValues") { + updateData = message.data; + updateValues(); + } else if (message.type == "startRefreshValues") { + updateData = message.data; + if (updateData.length > maxUpdatingMethods) { + updateData = message.data; + updateValues(); + } else { + needsUpdate = true; + deltaTime = updateTime; + } + } else if (message.type == "stopRefreshValues") { + needsUpdate = false; + deltaTime = 0; + } else if (message.type == "evaluateMember") { + var value = "" + try { + value = "" + eval(message.data.member); + } catch(e) { + value = "Error evaluating" + } + window.sendToQml({type:"evaluateMember", data:{value:value, index:message.data.index}}); + } else if (message.type == "selectScript") { + scriptPath = Window.browse("Select script to debug", "*.js", "JS files(*.js)"); + if (scriptPath) { + ScriptDiscoveryService.stopScript(Paths.defaultScripts + "/developer/utilities/tools/currentAPI.js", true); + } + } + }); + + function updateValues() { + for (var i = 0; i < updateData.length; i++) { + try { + updateData[i].value = "" + eval(updateData[i].member); + } catch(e) { + updateData[i].value = "Error evaluating" + } + } + window.sendToQml({type: "refreshValues", data: updateData}); + } + + Script.update.connect(function(){ + deltaTime++; + if (deltaTime > updateTime) { + deltaTime = 0; + if (needsUpdate) { + updateValues(); + } + } + }); + + Script.scriptEnding.connect(function(){ + if (!scriptPath || scriptPath.length == 0) { + ScriptDiscoveryService.debugScriptUrl = ""; + } else { + ScriptDiscoveryService.debugScriptUrl = scriptPath; + } + console.log("done running"); + window.close(); + }); +}()); -print(buffer); diff --git a/scripts/system/assets/animations/Cheering.fbx b/scripts/system/assets/animations/Cheering.fbx new file mode 100644 index 0000000000..8787bf4bd8 Binary files /dev/null and b/scripts/system/assets/animations/Cheering.fbx differ diff --git a/scripts/system/assets/animations/Clapping.fbx b/scripts/system/assets/animations/Clapping.fbx new file mode 100644 index 0000000000..d05b41866d Binary files /dev/null and b/scripts/system/assets/animations/Clapping.fbx differ diff --git a/scripts/system/assets/animations/Crying.fbx b/scripts/system/assets/animations/Crying.fbx new file mode 100644 index 0000000000..2e60ba2450 Binary files /dev/null and b/scripts/system/assets/animations/Crying.fbx differ diff --git a/scripts/system/assets/animations/Dancing.fbx b/scripts/system/assets/animations/Dancing.fbx new file mode 100644 index 0000000000..7759d273b7 Binary files /dev/null and b/scripts/system/assets/animations/Dancing.fbx differ diff --git a/scripts/system/assets/animations/Fall.fbx b/scripts/system/assets/animations/Fall.fbx new file mode 100644 index 0000000000..627e909bb4 Binary files /dev/null and b/scripts/system/assets/animations/Fall.fbx differ diff --git a/scripts/system/assets/animations/Pointing.fbx b/scripts/system/assets/animations/Pointing.fbx new file mode 100644 index 0000000000..da3c9bbeca Binary files /dev/null and b/scripts/system/assets/animations/Pointing.fbx differ diff --git a/scripts/system/assets/animations/Surprised.fbx b/scripts/system/assets/animations/Surprised.fbx new file mode 100644 index 0000000000..49362605b3 Binary files /dev/null and b/scripts/system/assets/animations/Surprised.fbx differ diff --git a/scripts/system/assets/animations/Waving.fbx b/scripts/system/assets/animations/Waving.fbx new file mode 100644 index 0000000000..e2442f64f4 Binary files /dev/null and b/scripts/system/assets/animations/Waving.fbx differ diff --git a/scripts/system/assets/sounds/rezzing.wav b/scripts/system/assets/sounds/rezzing.wav new file mode 100644 index 0000000000..3c059aecdf Binary files /dev/null and b/scripts/system/assets/sounds/rezzing.wav differ diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 16f1d086b7..7f2cbe0aee 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -148,8 +148,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.setIgnorePointerItems = function() { if (HMD.tabletID !== this.tabletID) { this.tabletID = HMD.tabletID; - Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist); - Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist); + Pointers.setIgnoreItems(_this.leftPointer, _this.blacklist.concat([HMD.tabletID])); + Pointers.setIgnoreItems(_this.rightPointer, _this.blacklist.concat([HMD.tabletID])); } }; @@ -378,8 +378,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }; this.setBlacklist = function() { - RayPick.setIgnoreItems(_this.leftControllerRayPick, this.blacklist); - RayPick.setIgnoreItems(_this.rightControllerRayPick, this.blacklist); + RayPick.setIgnoreItems(_this.leftControllerRayPick, this.blacklist.concat([HMD.tabletID])); + RayPick.setIgnoreItems(_this.rightControllerRayPick, this.blacklist.concat([HMD.tabletID])); }; var MAPPING_NAME = "com.highfidelity.controllerDispatcher"; diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 0e04f3b985..88195d7024 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -447,7 +447,9 @@ Script.include("/~/system/libraries/Xform.js"); this.targetObject = new TargetObject(entityID, targetProps); this.targetObject.parentProps = getEntityParents(targetProps); - Script.clearTimeout(this.contextOverlayTimer); + if (this.contextOverlayTimer) { + Script.clearTimeout(this.contextOverlayTimer); + } this.contextOverlayTimer = false; if (entityID !== this.entityWithContextOverlay) { this.destroyContextOverlay(); diff --git a/scripts/system/controllers/handTouch.js b/scripts/system/controllers/handTouch.js new file mode 100644 index 0000000000..345ba7a1d7 --- /dev/null +++ b/scripts/system/controllers/handTouch.js @@ -0,0 +1,739 @@ +// +// scripts/system/libraries/handTouch.js +// +// Created by Luis Cuenca on 12/29/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 +// + +/* jslint bitwise: true */ + +/* global Script, Overlays, Controller, Vec3, MyAvatar, Entities +*/ + +(function(){ + + var updateFingerWithIndex = 0; + + // Keys to access finger data + var fingerKeys = ["pinky", "ring", "middle", "index", "thumb"]; + + // Additionally close the hands to achieve a grabbing effect + var grabPercent = { left: 0, + right: 0 }; + + // var isGrabbing = false; + + var Palm = function() { + this.position = {x:0, y:0, z:0}; + this.perpendicular = {x:0, y:0, z:0}; + this.distance = 0; + this.fingers = { + pinky: {x:0, y:0, z:0}, + middle: {x:0, y:0, z:0}, + ring: {x:0, y:0, z:0}, + thumb: {x:0, y:0, z:0}, + index: {x:0, y:0, z:0} + }; + this.set = false; + }; + + var palmData = { + left: new Palm(), + right: new Palm() + }; + + var handJointNames = {left: "LeftHand", right: "RightHand"}; + + // Store which fingers are touching - if all false restate the default poses + var isTouching = { + left: { + pinky: false, + middle: false, + ring: false, + thumb: false, + index: false + }, right: { + pinky: false, + middle: false, + ring: false, + thumb: false, + index: false + } + }; + + // frame count for transition to default pose + + var countToDefault = { + left: 0, + right: 0 + }; + + // joint data for opened pose + + var dataOpen = { + left: { + pinky:[{x: -0.0066, y:-0.0224, z:-0.2174, w:0.9758},{x: 0.0112, y:0.0001, z:0.0093, w:0.9999},{x: -0.0346, y:0.0003, z:-0.0073, w:0.9994}], + ring:[{x: -0.0029, y:-0.0094, z:-0.1413, w:0.9899},{x: 0.0112, y:0.0001, z:0.0059, w:0.9999},{x: -0.0346, y:0.0002, z:-0.006, w:0.9994}], + middle:[{x: -0.0016, y:0, z:-0.0286, w:0.9996},{x: 0.0112, y:-0.0001, z:-0.0063, w:0.9999},{x: -0.0346, y:-0.0003, z:0.0073, w:0.9994}], + index:[{x: -0.0016, y:0.0001, z:0.0199, w:0.9998},{x: 0.0112, y:0, z:0.0081, w:0.9999},{x: -0.0346, y:0.0008, z:-0.023, w:0.9991}], + thumb:[{x: 0.0354, y:0.0363, z:0.3275, w:0.9435},{x: -0.0945, y:0.0938, z:0.0995, w:0.9861},{x: -0.0952, y:0.0718, z:0.1382, w:0.9832}] + }, right: { + pinky:[{x: -0.0034, y:0.023, z:0.1051, w:0.9942},{x: 0.0106, y:-0.0001, z:-0.0091, w:0.9999},{x: -0.0346, y:-0.0003, z:0.0075, w:0.9994}], + ring:[{x: -0.0013, y:0.0097, z:0.0311, w:0.9995},{x: 0.0106, y:-0.0001, z:-0.0056, w:0.9999},{x: -0.0346, y:-0.0002, z:0.0061, w:0.9994}], + middle:[{x: -0.001, y:0, z:0.0285, w:0.9996},{x: 0.0106, y:0.0001, z:0.0062, w:0.9999},{x: -0.0346, y:0.0003, z:-0.0074, w:0.9994}], + index:[{x: -0.001, y:0, z:-0.0199, w:0.9998},{x: 0.0106, y:-0.0001, z:-0.0079, w:0.9999},{x: -0.0346, y:-0.0008, z:0.0229, w:0.9991}], + thumb:[{x: 0.0355, y:-0.0363, z:-0.3263, w:0.9439},{x: -0.0946, y:-0.0938, z:-0.0996, w:0.9861},{x: -0.0952, y:-0.0719, z:-0.1376, w:0.9833}] + } + }; + var dataClose = { + left: { + pinky:[{x: 0.5878, y:-0.1735, z:-0.1123, w:0.7821},{x: 0.5704, y:0.0053, z:0.0076, w:0.8213},{x: 0.6069, y:-0.0044, z:-0.0058, w:0.7947}], + ring:[{x: 0.5761, y:-0.0989, z:-0.1025, w:0.8048},{x: 0.5332, y:0.0032, z:0.005, w:0.846},{x: 0.5773, y:-0.0035, z:-0.0049, w:0.8165}], + middle:[{x: 0.543, y:-0.0469, z:-0.0333, w:0.8378},{x: 0.5419, y:-0.0034, z:-0.0053, w:0.8404},{x: 0.5015, y:0.0037, z:0.0063, w:0.8651}], + index:[{x: 0.3051, y:-0.0156, z:-0.014, w:0.9521},{x: 0.6414, y:0.0051, z:0.0063, w:0.7671},{x: 0.5646, y:-0.013, z:-0.019, w:0.8251}], + thumb:[{x: 0.313, y:-0.0348, z:0.3192, w:0.8938},{x: 0, y:0, z:-0.37, w:0.929},{x: 0, y:0, z:-0.2604, w:0.9655}] + }, right: { + pinky:[{x: 0.5881, y:0.1728, z:0.1114, w:0.7823},{x: 0.5704, y:-0.0052, z:-0.0075, w:0.8213},{x: 0.6069, y:0.0046, z:0.006, w:0.7947}], + ring:[{x: 0.5729, y:0.1181, z:0.0898, w:0.8061},{x: 0.5332, y:-0.003, z:-0.0048, w:0.846},{x: 0.5773, y:0.0035, z:0.005, w:0.8165}], + middle:[{x: 0.543, y:0.0468, z:0.0332, w:0.8378},{x: 0.5419, y:0.0034, z:0.0052, w:0.8404},{x: 0.5047, y:-0.0037, z:-0.0064, w:0.8632}], + index:[{x: 0.306, y:-0.0076, z:-0.0584, w:0.9502},{x: 0.6409, y:-0.005, z:-0.006, w:0.7675},{x: 0.5646, y:0.0129, z:0.0189, w:0.8251}], + thumb:[{x: 0.313, y:0.0352, z:-0.3181, w:0.8942},{x: 0, y:0, z:0.3698, w:0.9291},{x: 0, y:0, z:0.2609, w:0.9654}] + } + }; + + // snapshot for the default pose + + var dataDefault = { + left:{ + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + set: false + }, + right:{ + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + set: false + } + }; + + // joint data for the current frame + + var dataCurrent = { + left:{ + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] + }, + right:{ + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] + } + }; + + // interpolated values on joint data to smooth movement + + var dataDelta = { + left:{ + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] + }, + right:{ + pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] + } + }; + + // Acquire an updated value per hand every 5 frames when finger is touching (faster in) + + var touchAnimationSteps = 5; + + // Acquire an updated value per hand every 10 frames when finger is returning to default position (slower out) + + var defaultAnimationSteps = 10; + + // Debugging info + + var showSphere = false; + var showLines = false; + + // This get setup on creation + + var linesCreated = false; + var sphereCreated = false; + + // Register object with API Debugger + + var varsToDebug = { + scriptLoaded: false, + toggleDebugSphere: function(){ + showSphere = !showSphere; + if (showSphere && !sphereCreated) { + createDebugSphere(); + sphereCreated = true; + } + }, + toggleDebugLines: function(){ + showLines = !showLines; + if (showLines && !linesCreated) { + createDebugLines(); + linesCreated = true; + } + }, + fingerPercent: { + left: { + pinky: 0.38, + middle: 0.38, + ring: 0.38, + thumb: 0.38, + index: 0.38 + } , + right: { + pinky: 0.38, + middle: 0.38, + ring: 0.38, + thumb: 0.38, + index: 0.38 + } + }, + triggerValues: { + leftTriggerValue: 0, + leftTriggerClicked: 0, + rightTriggerValue: 0, + rightTriggerClicked: 0, + leftSecondaryValue: 0, + rightSecondaryValue: 0 + }, + palmData: { + left: new Palm(), + right: new Palm() + }, + offset: {x:0, y:0, z:0}, + avatarLoaded: false + }; + + + // Add/Subtract the joint data - per finger joint + + function addVals(val1, val2, sign) { + var val = []; + if (val1.length != val2.length) return; + for (var i = 0; i < val1.length; i++) { + val.push({x: 0, y: 0, z: 0, w: 0}); + val[i].x = val1[i].x + sign*val2[i].x; + val[i].y = val1[i].y + sign*val2[i].y; + val[i].z = val1[i].z + sign*val2[i].z; + val[i].w = val1[i].w + sign*val2[i].w; + } + return val; + } + + // Multiply/Divide the joint data - per finger joint + + function multiplyValsBy(val1, num) { + var val = []; + for (var i = 0; i < val1.length; i++) { + val.push({x: 0, y: 0, z: 0, w: 0}); + val[i].x = val1[i].x * num; + val[i].y = val1[i].y * num; + val[i].z = val1[i].z * num; + val[i].w = val1[i].w * num; + } + return val; + } + + // Calculate the finger lengths by adding its joint lengths + + function getJointDistances(jointNamesArray) { + var result = {distances: [], totalDistance: 0}; + for (var i = 1; i < jointNamesArray.length; i++) { + var index0 = MyAvatar.getJointIndex(jointNamesArray[i-1]); + var index1 = MyAvatar.getJointIndex(jointNamesArray[i]); + var pos0 = MyAvatar.getJointPosition(index0); + var pos1 = MyAvatar.getJointPosition(index1); + var distance = Vec3.distance(pos0, pos1); + result.distances.push(distance); + result.totalDistance += distance; + } + return result; + } + + function dataRelativeToWorld(side, dataIn, dataOut) { + + var handJoint = handJointNames[side]; + var jointIndex = MyAvatar.getJointIndex(handJoint); + var worldPosHand = MyAvatar.jointToWorldPoint({x:0, y:0, z:0}, jointIndex); + + dataOut.position = MyAvatar.jointToWorldPoint(dataIn.position, jointIndex); + // dataOut.perpendicular = Vec3.subtract(MyAvatar.jointToWorldPoint(dataIn.perpendicular, jointIndex), worldPosHand); + var localPerpendicular = side == "right" ? {x:0.2, y:0, z:1} : {x:-0.2, y:0, z:1}; + dataOut.perpendicular = Vec3.normalize(Vec3.subtract(MyAvatar.jointToWorldPoint(localPerpendicular, jointIndex), worldPosHand)); + dataOut.distance = dataIn.distance; + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + dataOut.fingers[finger] = MyAvatar.jointToWorldPoint(dataIn.fingers[finger], jointIndex); + } + } + + function dataRelativeToHandJoint(side, dataIn, dataOut) { + + var handJoint = handJointNames[side]; + var jointIndex = MyAvatar.getJointIndex(handJoint); + var worldPosHand = MyAvatar.jointToWorldPoint({x:0, y:0, z:0}, jointIndex); + + dataOut.position = MyAvatar.worldToJointPoint(dataIn.position, jointIndex); + dataOut.perpendicular = MyAvatar.worldToJointPoint(Vec3.sum(worldPosHand, dataIn.perpendicular), jointIndex); + dataOut.distance = dataIn.distance; + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + dataOut.fingers[finger] = MyAvatar.worldToJointPoint(dataIn.fingers[finger], jointIndex); + } + } + + // Calculate the sphere that look up for entities, the center of the palm, perpendicular vector from the palm plane and origin of the the finger rays + + function estimatePalmData(side) { + // Return data object + var data = new Palm(); + + var jointOffset = { x: 0, y: 0, z: 0 }; + + var upperSide = side[0].toUpperCase() + side.substring(1); + var jointIndexHand = MyAvatar.getJointIndex(upperSide + "Hand"); + + // Store position of the hand joint + var worldPosHand = MyAvatar.jointToWorldPoint(jointOffset, jointIndexHand); + var minusWorldPosHand = {x:-worldPosHand.x, y:-worldPosHand.y, z:-worldPosHand.z}; + + // Data for finger rays + var directions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; + var positions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; + + var thumbLength = 0; + var weightCount = 0; + + // Calculate palm center + + var handJointWeight = 1; + var fingerJointWeight = 2; + + var palmCenter = {x:0, y:0, z:0}; + palmCenter = Vec3.sum(worldPosHand, palmCenter); + + weightCount += handJointWeight; + + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + var jointSuffixes = 4; // Get 4 joint names with suffix numbers (0, 1, 2, 3) + var jointNames = getJointNames(side, finger, jointSuffixes); + var fingerLength = getJointDistances(jointNames).totalDistance; + + var jointIndex = MyAvatar.getJointIndex(jointNames[0]); + positions[finger] = MyAvatar.jointToWorldPoint(jointOffset, jointIndex); + directions[finger] = Vec3.normalize(Vec3.sum(positions[finger], minusWorldPosHand)); + data.fingers[finger] = Vec3.sum(positions[finger], Vec3.multiply(fingerLength, directions[finger])); + if (finger != "thumb") { + // finger joints have double the weight than the hand joint + // This would better position the palm estimation + + palmCenter = Vec3.sum(Vec3.multiply(fingerJointWeight, positions[finger]), palmCenter); + weightCount += fingerJointWeight; + } else { + thumbLength = fingerLength; + } + } + + // perpendicular change direction depending on the side + + data.perpendicular = (side == "right") ? + Vec3.normalize(Vec3.cross(directions.index, directions.pinky)): + Vec3.normalize(Vec3.cross(directions.pinky, directions.index)); + + data.position = Vec3.multiply(1.0/weightCount, palmCenter); + + if (side == "right") varsToDebug.offset = MyAvatar.worldToJointPoint(worldPosHand, jointIndexHand); + + var palmDistanceMultiplier = 1.55; // 1.55 based on test/error for the sphere radius that best fits the hand + data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions.index); + + // move back thumb ray origin + var thumbBackMultiplier = 0.2; + data.fingers.thumb = Vec3.sum(data.fingers.thumb, Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular)); + + //return getDataRelativeToHandJoint(side, data); + dataRelativeToHandJoint(side, data, palmData[side]); + palmData[side].set = true; + // return palmData[side]; + } + + // Register GlobalDebugger for API Debugger + Script.registerValue("GlobalDebugger", varsToDebug); + + + + // store the rays for the fingers - only for debug purposes + + var fingerRays = { + left:{ + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, + index: undefined + }, + right:{ + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, + index: undefined + } + }; + + // Create debug overlays - finger rays + palm rays + spheres + + var palmRay, sphereHand; + + function createDebugLines() { + + for (var i = 0; i < fingerKeys.length; i++) { + fingerRays.left[fingerKeys[i]] = Overlays.addOverlay("line3d", { + color: { red: 0, green: 0, blue: 255 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }); + fingerRays.right[fingerKeys[i]] = Overlays.addOverlay("line3d", { + color: { red: 0, green: 0, blue: 255 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }); + } + + palmRay = { + left: Overlays.addOverlay("line3d", { + color: { red: 255, green: 0, blue: 0 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }), + right: Overlays.addOverlay("line3d", { + color: { red: 255, green: 0, blue: 0 }, + start: { x:0, y:0, z:0 }, + end: { x:0, y:1, z:0 }, + visible: showLines + }) + }; + linesCreated = true; + } + + function createDebugSphere() { + + sphereHand = { + right: Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: { red: 0, green: 255, blue: 0 }, + scale: { x: 0.01, y: 0.01, z: 0.01 }, + visible: showSphere + }), + left: Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: { red: 0, green: 255, blue: 0 }, + scale: { x: 0.01, y: 0.01, z: 0.01 }, + visible: showSphere + }) + }; + sphereCreated = true; + } + + function acquireDefaultPose(side) { + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + var jointSuffixes = 3; // We need rotation of the 0, 1 and 2 joints + var names = getJointNames(side, finger, jointSuffixes); + for (var j = 0; j < names.length; j++) { + var index = MyAvatar.getJointIndex(names[j]); + var rotation = MyAvatar.getJointRotation(index); + dataDefault[side][finger][j] = dataCurrent[side][finger][j] = rotation; + } + } + dataDefault[side].set = true; + } + + function updateSphereHand(side) { + + var data = new Palm(); + dataRelativeToWorld(side, palmData[side], data); + varsToDebug.palmData[side] = palmData[side]; + + var palmPoint = data.position; + var LOOKUP_DISTANCE_MULTIPLIER = 1.5; + var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance; + + // Situate the debugging overlays + + var checkOffset = { x: data.perpendicular.x * dist, + y: data.perpendicular.y * dist, + z: data.perpendicular.z * dist }; + + + var spherePos = Vec3.sum(palmPoint, checkOffset); + var checkPoint = Vec3.sum(palmPoint, Vec3.multiply(2, checkOffset)); + + if (showLines) { + Overlays.editOverlay(palmRay[side], { + start: palmPoint, + end: checkPoint, + visible: showLines + }); + for (var i = 0; i < fingerKeys.length; i++) { + Overlays.editOverlay(fingerRays[side][fingerKeys[i]], { + start: data.fingers[fingerKeys[i]], + end: checkPoint, + visible: showLines + }); + } + } + + if (showSphere) { + Overlays.editOverlay(sphereHand[side], { + position: spherePos, + scale: { + x: 2*dist, + y: 2*dist, + z: 2*dist + }, + visible: showSphere + }); + } + + // Update the intersection of only one finger at a time + + var finger = fingerKeys[updateFingerWithIndex]; + + var grabbables = Entities.findEntities(spherePos, dist); + var newFingerData = dataDefault[side][finger]; + var animationSteps = defaultAnimationSteps; + + if (grabbables.length > 0) { + var origin = data.fingers[finger]; + var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); + var intersection = Entities.findRayIntersection({origin: origin, direction: direction}, true, grabbables, [], true, false); + var percent = 0; // Initialize + var isAbleToGrab = intersection.intersects && intersection.distance < LOOKUP_DISTANCE_MULTIPLIER*dist; + if (isAbleToGrab && !getTouching(side)) { + acquireDefaultPose(side); // take a snapshot of the default pose before touch starts + newFingerData = dataDefault[side][finger]; // assign default pose to finger data + } + // Store if this finger is touching something + isTouching[side][finger] = isAbleToGrab; + if (isAbleToGrab) { + // update the open/close percentage for this finger + + var FINGER_REACT_MULTIPLIER = 2.8; + + percent = intersection.distance/(FINGER_REACT_MULTIPLIER*dist); + + var THUMB_FACTOR = 0.2; + var FINGER_FACTOR = 0.05; + + var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR; // Amount of grab coefficient added to the fingers - thumb is higher + percent += grabMultiplier * grabPercent[side]; + + // Calculate new interpolation data + var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1); + newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); // assign close/open ratio to finger to simulate touch + animationSteps = touchAnimationSteps; + } + varsToDebug.fingerPercent[side][finger] = percent; + } + + // Calculate animation increments + dataDelta[side][finger] = multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); + + } + + // Recreate the finger joint names + + function getJointNames(side, finger, count) { + var names = []; + for (var i = 1; i < count+1; i++) { + var name = side[0].toUpperCase()+side.substring(1)+"Hand"+finger[0].toUpperCase()+finger.substring(1)+(i); + names.push(name); + } + return names; + } + + // Capture the controller values + + var leftTriggerPress = function (value) { + varsToDebug.triggerValues.leftTriggerValue = value; + // the value for the trigger increments the hand-close percentage + grabPercent.left = value; + }; + var leftTriggerClick = function (value) { + varsToDebug.triggerValues.leftTriggerClicked = value; + }; + var rightTriggerPress = function (value) { + varsToDebug.triggerValues.rightTriggerValue = value; + // the value for the trigger increments the hand-close percentage + grabPercent.right = value; + }; + var rightTriggerClick = function (value) { + varsToDebug.triggerValues.rightTriggerClicked = value; + }; + var leftSecondaryPress = function (value) { + varsToDebug.triggerValues.leftSecondaryValue = value; + }; + var rightSecondaryPress = function (value) { + varsToDebug.triggerValues.rightSecondaryValue = value; + }; + + var MAPPING_NAME = "com.highfidelity.handTouch"; + var mapping = Controller.newMapping(MAPPING_NAME); + mapping.from([Controller.Standard.RT]).peek().to(rightTriggerPress); + mapping.from([Controller.Standard.RTClick]).peek().to(rightTriggerClick); + mapping.from([Controller.Standard.LT]).peek().to(leftTriggerPress); + mapping.from([Controller.Standard.LTClick]).peek().to(leftTriggerClick); + + mapping.from([Controller.Standard.RB]).peek().to(rightSecondaryPress); + mapping.from([Controller.Standard.LB]).peek().to(leftSecondaryPress); + mapping.from([Controller.Standard.LeftGrip]).peek().to(leftSecondaryPress); + mapping.from([Controller.Standard.RightGrip]).peek().to(rightSecondaryPress); + + Controller.enableMapping(MAPPING_NAME); + + if (showLines && !linesCreated) { + createDebugLines(); + linesCreated = true; + } + if (showSphere && !sphereCreated) { + createDebugSphere(); + sphereCreated = true; + } + + function getTouching(side) { + var animating = false; + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + animating = animating || isTouching[side][finger]; + } + return animating; // return false only if none of the fingers are touching + } + + function reEstimatePalmData() { + ["right", "left"].forEach(function(side){ + estimatePalmData(side); + }); + } + + MyAvatar.onLoadComplete.connect(function () { + // Sometimes the rig is not ready when this signal is trigger + console.log("avatar loaded"); + Script.setInterval(function(){ + reEstimatePalmData(); + }, 2000); + }); + + MyAvatar.sensorToWorldScaleChanged.connect(function(){ + reEstimatePalmData(); + }); + + Script.scriptEnding.connect(function () { + ["right", "left"].forEach(function(side){ + if (linesCreated) { + Overlays.deleteOverlay(palmRay[side]); + } + if (sphereCreated) { + Overlays.deleteOverlay(sphereHand[side]); + } + for (var i = 0; i < fingerKeys.length; i++) { + + var finger = fingerKeys[i]; + var jointSuffixes = 3; // We need to clear the joints 0, 1 and 2 joints + var names = getJointNames(side, finger, jointSuffixes); + + for (var j = 0; j < names.length; j++) { + var index = MyAvatar.getJointIndex(names[j]); + MyAvatar.clearJointData(index); + } + + if (linesCreated) { + Overlays.deleteOverlay(fingerRays[side][finger]); + } + } + }); + + + + }); + + Script.update.connect(function(){ + + // index of the finger that needs to be updated this frame + + + + updateFingerWithIndex = (updateFingerWithIndex < fingerKeys.length-1) ? updateFingerWithIndex + 1 : 0; + + + ["right", "left"].forEach(function(side){ + + if (!palmData[side].set) { + reEstimatePalmData(); + } + // recalculate the base data + updateSphereHand(side); + + // this vars manage the transition to default pose + var isHandTouching = getTouching(side); + countToDefault[side] = isHandTouching ? 0 : countToDefault[side] + 1; + + + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + var jointSuffixes = 3; // We need to update rotation of the 0, 1 and 2 joints + var names = getJointNames(side, finger, jointSuffixes); + + // Add the animation increments + + dataCurrent[side][finger] = addVals(dataCurrent[side][finger], dataDelta[side][finger], 1); + + // update every finger joint + + for (var j = 0; j < names.length; j++) { + var index = MyAvatar.getJointIndex(names[j]); + // if no finger is touching restate the default poses + if (isHandTouching || (dataDefault[side].set && countToDefault[side] < 5*touchAnimationSteps)) { + var quatRot = dataCurrent[side][finger][j]; + MyAvatar.setJointRotation(index, quatRot); + } else { + MyAvatar.clearJointData(index); + } + } + } + }); + }); + +}()); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index e28f877d85..87cd3e0faf 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -225,7 +225,7 @@ function adjustPositionPerBoundingBox(position, direction, registration, dimensi var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; -var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable"; +var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; var toolBar = (function () { var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts @@ -239,6 +239,7 @@ var toolBar = (function () { var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS; var position = getPositionToCreateEntity(); var entityID = null; + if (position !== null && position !== undefined) { var direction; if (Camera.mode === "entity" || Camera.mode === "independent") { @@ -278,9 +279,13 @@ var toolBar = (function () { position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions); properties.position = position; - if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) { + if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM) && + !(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) { properties.userData = JSON.stringify({ grabbableKey: { grabbable: true } }); + } else { + properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } }); } + entityID = Entities.addEntity(properties); if (properties.type === "ParticleEffect") { @@ -2044,9 +2049,9 @@ var PropertiesTool = function (opts) { // If any of the natural dimensions are not 0, resize if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 && - naturalDimensions.z === 0) { + naturalDimensions.z === 0) { Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" + - " is invalid or the model has not yet been loaded."); + " is invalid or the model has not yet been loaded."); } else { Entities.editEntity(selectionManager.selections[i], { dimensions: properties.naturalDimensions diff --git a/scripts/system/emote.js b/scripts/system/emote.js new file mode 100644 index 0000000000..f1f739c126 --- /dev/null +++ b/scripts/system/emote.js @@ -0,0 +1,122 @@ +"use strict"; + +// +// emote.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 7 Jan 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals Script, Tablet */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + + +var EMOTE_ANIMATIONS = ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping']; +var ANIMATIONS = Array(); + + +EMOTE_ANIMATIONS.forEach(function (name) { + var animationURL = Script.resolvePath("assets/animations/" + name + ".fbx"); + var resource = AnimationCache.prefetch(animationURL); + var animation = AnimationCache.getAnimation(animationURL); + ANIMATIONS[name] = { url: animationURL, animation: animation, resource: resource}; +}); + + +var EMOTE_APP_BASE = "html/EmoteApp.html"; +var EMOTE_APP_URL = Script.resolvePath(EMOTE_APP_BASE); +var EMOTE_LABEL = "EMOTE"; +var EMOTE_APP_SORT_ORDER = 11; +var FPS = 60; +var MSEC_PER_SEC = 1000; +var FINISHED = 3; // see ScriptableResource::State + +var onEmoteScreen = false; +var button; +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); +var activeTimer = false; // used to cancel active timer if a user plays an amimation while another animation is playing +var activeEmote = false; // to keep track of the currently playing emote + +button = tablet.addButton({ + //icon: "icons/tablet-icons/emote.svg", // TODO - we need graphics for this + text: EMOTE_LABEL, + sortOrder: EMOTE_APP_SORT_ORDER +}); + +function onClicked() { + if (onEmoteScreen) { + tablet.gotoHomeScreen(); + } else { + onEmoteScreen = true; + tablet.gotoWebScreen(EMOTE_APP_URL); + } +} + +function onScreenChanged(type, url) { + onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) == url.length - EMOTE_APP_BASE.length); + button.editProperties({ isActive: onEmoteScreen }); +} + +// Handle the events we're receiving from the web UI +function onWebEventReceived(event) { + + // Converts the event to a JavasScript Object + if (typeof event === "string") { + event = JSON.parse(event); + } + + if (event.type === "click") { + var emoteName = event.data; + + if (ANIMATIONS[emoteName].resource.state == FINISHED) { + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); + } + + // if the activeEmote is different from the chosen emote, then play the new emote. Other wise, + // this is a second click on the same emote as the activeEmote, and we will just stop it. + if (activeEmote !== emoteName) { + activeEmote = emoteName; + var frameCount = ANIMATIONS[emoteName].animation.frames.length; + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + var timeOut = MSEC_PER_SEC * frameCount / FPS; + activeTimer = Script.setTimeout(function () { + MyAvatar.restoreAnimation(); + activeTimer = false; + activeEmote = false; + }, timeOut); + } else { + activeEmote = false; + MyAvatar.restoreAnimation(); + } + } + } +} + +button.clicked.connect(onClicked); +tablet.screenChanged.connect(onScreenChanged); +tablet.webEventReceived.connect(onWebEventReceived); + +Script.scriptEnding.connect(function () { + if (onEmoteScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + if (tablet) { + tablet.removeButton(button); + } + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); + MyAvatar.restoreAnimation(); + } +}); + + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/html/EmoteApp.html b/scripts/system/html/EmoteApp.html new file mode 100644 index 0000000000..30ef3e17a1 --- /dev/null +++ b/scripts/system/html/EmoteApp.html @@ -0,0 +1,136 @@ + + + + Emote App + + + + + + +
+

Emote App

+
+
+

Click an emotion to Emote:

+

+

+

+

+

+

+

+

+
+ + + + + \ No newline at end of file diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 2ccad2c169..b93974ee77 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -490,10 +490,6 @@ ZoneM
-
- - -
@@ -508,9 +504,14 @@
-
+
+
+ Inherit + Off + On +
@@ -531,23 +532,67 @@
-
+
+ + Skybox + +
+ Inherit + Off + On +
+
+
+ Skybox color +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+ +
+
+
+ +
+
+ Inherit + Off + On +
+
-
+
- + Haze
- Inherit - Off - On + Inherit + Off + On
@@ -632,62 +677,7 @@
-
- - Stage - -
-
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
- - Background - - -
-
- - Skybox - -
-
- Skybox color -
-
-
-
-
-
-
- - -
-
- -
TextM diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 0ab9f7a9cb..72092b66ca 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -83,6 +83,7 @@ function disableProperties() { function showElements(els, show) { for (var i = 0; i < els.length; i++) { els[i].style.display = (show) ? 'table' : 'none'; + } } @@ -627,7 +628,6 @@ function loaded() { var elHyperlinkHref = document.getElementById("property-hyperlink-href"); - var elTextText = document.getElementById("property-text-text"); var elTextLineHeight = document.getElementById("property-text-line-height"); var elTextTextColor = document.getElementById("property-text-text-color"); @@ -639,18 +639,35 @@ function loaded() { var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green"); var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue"); - var elZoneStageSunModelEnabled = document.getElementById("property-zone-stage-sun-model-enabled"); + // Key light + var elZoneKeyLightModeInherit = document.getElementById("property-zone-key-light-mode-inherit"); + var elZoneKeyLightModeDisabled = document.getElementById("property-zone-key-light-mode-disabled"); + var elZoneKeyLightModeEnabled = document.getElementById("property-zone-key-light-mode-enabled"); var elZoneKeyLightColor = document.getElementById("property-zone-key-light-color"); var elZoneKeyLightColorRed = document.getElementById("property-zone-key-light-color-red"); var elZoneKeyLightColorGreen = document.getElementById("property-zone-key-light-color-green"); var elZoneKeyLightColorBlue = document.getElementById("property-zone-key-light-color-blue"); var elZoneKeyLightIntensity = document.getElementById("property-zone-key-intensity"); - var elZoneKeyLightAmbientIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); - var elZoneKeyLightAmbientURL = document.getElementById("property-zone-key-ambient-url"); + // Skybox + var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); + var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); + var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); + + // Ambient light + var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); + + var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); + var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); + var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); + + var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); + var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); + + // Haze var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit"); var elZoneHazeModeDisabled = document.getElementById("property-zone-haze-mode-disabled"); var elZoneHazeModeEnabled = document.getElementById("property-zone-haze-mode-enabled"); @@ -673,15 +690,6 @@ function loaded() { var elZoneHazeBackgroundBlend = document.getElementById("property-zone-haze-background-blend"); - var elZoneStageLatitude = document.getElementById("property-zone-stage-latitude"); - var elZoneStageLongitude = document.getElementById("property-zone-stage-longitude"); - var elZoneStageAltitude = document.getElementById("property-zone-stage-altitude"); - var elZoneStageAutomaticHourDay = document.getElementById("property-zone-stage-automatic-hour-day"); - var elZoneStageDay = document.getElementById("property-zone-stage-day"); - var elZoneStageHour = document.getElementById("property-zone-stage-hour"); - - var elZoneBackgroundMode = document.getElementById("property-zone-background-mode"); - var elZoneSkyboxColor = document.getElementById("property-zone-skybox-color"); var elZoneSkyboxColorRed = document.getElementById("property-zone-skybox-color-red"); var elZoneSkyboxColorGreen = document.getElementById("property-zone-skybox-color-green"); @@ -1002,22 +1010,39 @@ function loaded() { elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); elLightExponent.value = properties.exponent.toFixed(2); elLightCutoff.value = properties.cutoff.toFixed(2); + } else if (properties.type === "Zone") { - elZoneStageSunModelEnabled.checked = properties.stage.sunModelEnabled; + // Key light + elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); + elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); + elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); + elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; elZoneKeyLightColorRed.value = properties.keyLight.color.red; elZoneKeyLightColorGreen.value = properties.keyLight.color.green; elZoneKeyLightColorBlue.value = properties.keyLight.color.blue; elZoneKeyLightIntensity.value = properties.keyLight.intensity.toFixed(2); - elZoneKeyLightAmbientIntensity.value = properties.keyLight.ambientIntensity.toFixed(2); elZoneKeyLightDirectionX.value = properties.keyLight.direction.x.toFixed(2); elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); - elZoneKeyLightAmbientURL.value = properties.keyLight.ambientURL; - elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); + // Skybox + elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); + elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); + elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); + + // Ambient light + elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); + elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); + elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); + + elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); + elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; + + // Haze + elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); - elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); + elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); elZoneHazeColor.style.backgroundColor = "rgb(" + @@ -1047,19 +1072,9 @@ function loaded() { elZoneHazeCeiling.value = properties.haze.hazeCeiling.toFixed(0); elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2); - - elZoneStageLatitude.value = properties.stage.latitude.toFixed(2); - elZoneStageLongitude.value = properties.stage.longitude.toFixed(2); - elZoneStageAltitude.value = properties.stage.altitude.toFixed(2); - elZoneStageAutomaticHourDay.checked = properties.stage.automaticHourDay; - elZoneStageDay.value = properties.stage.day; - elZoneStageHour.value = properties.stage.hour; elShapeType.value = properties.shapeType; elCompoundShapeURL.value = properties.compoundShapeURL; - elZoneBackgroundMode.value = properties.backgroundMode; - setDropdownText(elZoneBackgroundMode); - elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," + properties.skybox.color.green + "," + properties.skybox.color.blue + ")"; elZoneSkyboxColorRed.value = properties.skybox.color.red; @@ -1071,8 +1086,16 @@ function loaded() { elZoneGhostingAllowed.checked = properties.ghostingAllowed; elZoneFilterURL.value = properties.filterURL; - showElements(document.getElementsByClassName('skybox-section'), - elZoneBackgroundMode.value === 'skybox'); + // Show/hide sections as required + showElements(document.getElementsByClassName('skybox-section'), + elZoneSkyboxModeEnabled.checked); + showElements(document.getElementsByClassName('keylight-section'), + elZoneKeyLightModeEnabled.checked); + showElements(document.getElementsByClassName('ambient-section'), + elZoneAmbientLightModeEnabled.checked); + showElements(document.getElementsByClassName('haze-section'), + elZoneHazeModeEnabled.checked); + } else if (properties.type === "PolyVox") { elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); @@ -1380,6 +1403,7 @@ function loaded() { var textBackgroundColorChangeFunction = createEmitColorPropertyUpdateFunction( 'backgroundColor', elTextBackgroundColorRed, elTextBackgroundColorGreen, elTextBackgroundColorBlue); + elTextBackgroundColorRed.addEventListener('change', textBackgroundColorChangeFunction); elTextBackgroundColorGreen.addEventListener('change', textBackgroundColorChangeFunction); elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction); @@ -1400,8 +1424,14 @@ function loaded() { } })); - elZoneStageSunModelEnabled.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('stage', 'sunModelEnabled')); + // Key light + var keyLightModeChanged = createZoneComponentModeChangedFunction('keyLightMode', + elZoneKeyLightModeInherit, elZoneKeyLightModeDisabled, elZoneKeyLightModeEnabled); + + elZoneKeyLightModeInherit.addEventListener('change', keyLightModeChanged); + elZoneKeyLightModeDisabled.addEventListener('change', keyLightModeChanged); + elZoneKeyLightModeEnabled.addEventListener('change', keyLightModeChanged); + colorPickers.push($('#property-zone-key-light-color').colpick({ colorScheme: 'dark', layout: 'hex', @@ -1420,24 +1450,51 @@ function loaded() { })); var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); + elZoneKeyLightColorRed.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightColorGreen.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); - elZoneKeyLightAmbientIntensity.addEventListener('change', - createEmitGroupNumberPropertyUpdateFunction('keyLight', 'ambientIntensity')); - elZoneKeyLightAmbientURL.addEventListener('change', - createEmitGroupTextPropertyUpdateFunction('keyLight', 'ambientURL')); - var zoneKeyLightDirectionChangeFunction = - createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', - elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); + + var zoneKeyLightDirectionChangeFunction = createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', + elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); + elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); - var hazeModeChanged = - createZoneComponentModeChangedFunction('hazeMode', elZoneHazeModeInherit, - elZoneHazeModeDisabled, elZoneHazeModeEnabled); + // Skybox + var skyboxModeChanged = createZoneComponentModeChangedFunction('skyboxMode', + elZoneSkyboxModeInherit, elZoneSkyboxModeDisabled, elZoneSkyboxModeEnabled); + + elZoneSkyboxModeInherit.addEventListener('change', skyboxModeChanged); + elZoneSkyboxModeDisabled.addEventListener('change', skyboxModeChanged); + elZoneSkyboxModeEnabled.addEventListener('change', skyboxModeChanged); + + // Ambient light + elCopySkyboxURLToAmbientURL.addEventListener("click", function () { + document.getElementById("property-zone-key-ambient-url").value = properties.skybox.url; + properties.ambientLight.ambientURL = properties.skybox.url; + updateProperties(properties); + }); + + var ambientLightModeChanged = createZoneComponentModeChangedFunction('ambientLightMode', + elZoneAmbientLightModeInherit, elZoneAmbientLightModeDisabled, elZoneAmbientLightModeEnabled); + + elZoneAmbientLightModeInherit.addEventListener('change', ambientLightModeChanged); + elZoneAmbientLightModeDisabled.addEventListener('change', ambientLightModeChanged); + elZoneAmbientLightModeEnabled.addEventListener('change', ambientLightModeChanged); + + elZoneAmbientLightIntensity.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('ambientLight', 'ambientIntensity')); + + elZoneAmbientLightURL.addEventListener('change', + createEmitGroupTextPropertyUpdateFunction('ambientLight', 'ambientURL')); + + // Haze + var hazeModeChanged = createZoneComponentModeChangedFunction('hazeMode', + elZoneHazeModeInherit, elZoneHazeModeDisabled, elZoneHazeModeEnabled); + elZoneHazeModeInherit.addEventListener('change', hazeModeChanged); elZoneHazeModeDisabled.addEventListener('change', hazeModeChanged); elZoneHazeModeEnabled.addEventListener('change', hazeModeChanged); @@ -1506,16 +1563,6 @@ function loaded() { elZoneHazeBackgroundBlend.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend')); - elZoneStageLatitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'latitude')); - elZoneStageLongitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'longitude')); - elZoneStageAltitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'altitude')); - elZoneStageAutomaticHourDay.addEventListener('change', - createEmitGroupCheckedPropertyUpdateFunction('stage', 'automaticHourDay')); - elZoneStageDay.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'day')); - elZoneStageHour.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'hour')); - - elZoneBackgroundMode.addEventListener('change', createEmitTextPropertyUpdateFunction('backgroundMode')); - var zoneSkyboxColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('skybox', 'color', elZoneSkyboxColorRed, elZoneSkyboxColorGreen, elZoneSkyboxColorBlue); elZoneSkyboxColorRed.addEventListener('change', zoneSkyboxColorChangeFunction); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index a5360974f6..8819960354 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -28,6 +28,7 @@ var selectionDisplay = null; // for gridTool.js to ignore var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; + var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav")); var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; @@ -341,6 +342,15 @@ var selectionDisplay = null; // for gridTool.js to ignore // we currently assume a wearable is a single entity Entities.editEntity(pastedEntityIDs[0], offsets); } + + var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position; + + Audio.playSound(REZZING_SOUND, { + volume: 1.0, + position: rezPosition, + localOnly: true + }); + } else { Window.notifyEditError("Can't import entities: entities would be out of bounds."); } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 9afdb4ec53..dad642075f 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -121,7 +121,7 @@ function onMessage(message) { || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog"); } else { - tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); + tablet.loadQMLOnTop("hifi/tablet/TabletGeneralPreferences.qml"); } break; case 'captureStillAndGif':