diff --git a/INSTALL.md b/INSTALL.md index e07d28a43d..90e8712b19 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -19,6 +19,8 @@ To produce an executable installer on Windows, the following are required: - [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 - [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0 - [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0 +- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7 +- [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in) Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System. diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 3c2e660cbc..6963f4df0d 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -115,11 +115,7 @@ public: uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time); - QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { - auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar]; - lastOtherAvatarSentJoints.resize(_avatar->getJointCount()); - return lastOtherAvatarSentJoints; - } + QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; } void queuePacket(QSharedPointer message, SharedNodePointer node); int processPackets(); // returns number of packets processed diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index fb4b65726a..6f19b73cc5 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -381,6 +381,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) bool includeThisAvatar = true; auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); + + lastSentJointsForOther.resize(otherAvatar->getJointCount()); + bool distanceAdjust = true; glm::vec3 viewerPosition = myPosition; AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index e3aca52ee5..70fad03d67 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -380,10 +380,15 @@ void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const } void EntityServer::trackViewerGone(const QUuid& sessionID) { - QWriteLocker locker(&_viewerSendingStatsLock); - _viewerSendingStats.remove(sessionID); + { + QWriteLocker locker(&_viewerSendingStatsLock); + _viewerSendingStats.remove(sessionID); + } + if (_entitySimulation) { - _entitySimulation->clearOwnership(sessionID); + _tree->withReadLock([&] { + _entitySimulation->clearOwnership(sessionID); + }); } } diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt index f2690e0a7d..7bf6f05d9f 100644 --- a/cmake/externals/quazip/CMakeLists.txt +++ b/cmake/externals/quazip/CMakeLists.txt @@ -41,6 +41,9 @@ if (APPLE) elseif (WIN32) set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library") set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library") +elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.so CACHE FILEPATH "Location of QuaZip release library") else () set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library") diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt index a08b589ec2..cad6d40b49 100644 --- a/cmake/externals/serverless-content/CMakeLists.txt +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v4.zip - URL_MD5 d4f42f630986c83427ff39e1fe9908c6 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC67-v4.zip + URL_MD5 ba32aed18bfeaac4ccaf5ebb8ea3e804 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 28ac320e42..fc9b9ab03d 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -87,6 +87,10 @@ ;-------------------------------- ;-------------------------------- ;General + + ; hide install details since we show an image slideshow in their place + ShowInstDetails nevershow + ; leverage the UAC NSIS plugin to promote uninstaller to elevated privileges !include UAC.nsh @@ -446,6 +450,7 @@ SectionEnd Page custom PostInstallOptionsPage ReadPostInstallOptions !define MUI_PAGE_CUSTOMFUNCTION_PRE PageInstallFilesPre + !define MUI_PAGE_CUSTOMFUNCTION_SHOW StartInstallSlideshow !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_CONFIRM @@ -544,11 +549,33 @@ Var Express ${EndIf} !macroend +!macro DownloadSlideshowImages + InitPluginsDir + + Push $0 + + ; figure out where to download installer slideshow images from + StrCpy $0 "http://cdn.highfidelity.com/installer/slideshow" + + ${If} $CampaignName == "" + StrCpy $0 "$0/default" + ${Else} + StrCpy $0 "$0/$CampaignName" + ${EndIf} + + NSISdl::download_quiet $0/1.jpg "$PLUGINSDIR\1.jpg" + NSISdl::download_quiet $0/2.jpg "$PLUGINSDIR\2.jpg" + NSISdl::download_quiet $0/3.jpg "$PLUGINSDIR\3.jpg" + + Pop $0 +!macroend + Function OnUserAbort !insertmacro GoogleAnalytics "Installer" "Abort" "User Abort" "" FunctionEnd Function PageWelcomePre !insertmacro GoogleAnalytics "Installer" "Welcome" "" "" + !insertmacro DownloadSlideshowImages FunctionEnd Function PageLicensePre !insertmacro GoogleAnalytics "Installer" "License" "" "" @@ -640,6 +667,56 @@ Function ChangeCustomLabel Pop $R1 FunctionEnd +!macro AddImageToSlideshowFile ImageFilename + ${If} ${FileExists} "$PLUGINSDIR\${ImageFilename}.jpg" + FileWrite $0 "= ${ImageFilename}.jpg,500,5000,$\"$\"$\r$\n" + StrCpy $1 "1" + ${EndIf} +!macroend + +Function StartInstallSlideshow + ; create a slideshow file based on what files we have available + + ; stash $0 and $1 + Push $0 + Push $1 + + ; start $1 as 0, indicating we have no images present + StrCpy $1 "0" + + FileOpen $0 "$PLUGINSDIR\slideshow.dat" w + + ; write the language value to the slideshow file for english + FileWrite $0 "[1033]$\r$\n" + + ; for each of 1.jpg, 2.jpg, 3.jpg + ; if the image is present add it to the dat file and set our flag + ; to show we found at least one image + !insertmacro AddImageToSlideshowFile "1" + !insertmacro AddImageToSlideshowFile "2" + !insertmacro AddImageToSlideshowFile "3" + + FileClose $0 + + ; NOTE: something inside of nsisSlideshow::show isn't keeping the stack clean + ; so we need to push things back BEFORE we call it + + ${If} $1 == "1" + Pop $1 + Pop $0 + + ; show the slideshow using the created data file + nsisSlideshow::show /NOUNLOAD "/auto=$PLUGINSDIR\slideshow.dat" + ${Else} + Pop $1 + Pop $0 + + ; show the install details because we didn't end up with slideshow images to show + SetDetailsView show + ${EndIf} + +FunctionEnd + Function PostInstallOptionsPage !insertmacro MaybeSkipPage !insertmacro GoogleAnalytics "Installer" "Post Install Options" "" "" @@ -928,10 +1005,28 @@ Function HandlePostInstallOptions ${EndIf} FunctionEnd +Function OptionallyDownloadCampaignServerless + ${If} $CampaignName != "" + InitPluginsDir + + NSISdl::download_quiet http://cdn.highfidelity.com/installer/serverless/$CampaignName.zip $PLUGINSDIR\$CampaignName.zip + + ${If} ${FileExists} $PLUGINSDIR\$CampaignName.zip + ; replace the installed serverless content with the campaign content + + RMDir /r "$INSTDIR\resources\serverless" + CreateDirectory "$INSTDIR\resources\serverless" + nsisunz::Unzip "$PLUGINSDIR\$CampaignName.zip" "$INSTDIR\resources\serverless" + ${EndIf} + + ${Endif} +FunctionEnd + ;-------------------------------- ;Installer Sections Section "-Core installation" + ;The following delete blocks are temporary and can be removed once users who had the initial installer have updated ;Delete any server-console files installed before it was placed in sub-folder @@ -983,11 +1078,13 @@ Section "-Core installation" WriteRegStr HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR ;Write some information about this install to the installation folder + Push $0 FileOpen $0 "$INSTDIR\installer.ini" w FileWrite $0 "type=@INSTALLER_TYPE@$\r$\n" FileWrite $0 "campaign=$CampaignName$\r$\n" FileWrite $0 "exepath=$EXEPATH$\r$\n" FileClose $0 + Pop $0 ;Package the signed uninstaller produced by the inner loop !ifndef INNER @@ -1078,6 +1175,9 @@ Section "-Core installation" @CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ + ; see if we have a campaign that we might need to grab special content for + Call OptionallyDownloadCampaignServerless + ; Handle whichever post install options were set Call HandlePostInstallOptions diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index ac157979bb..baeac043e4 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -617,7 +617,7 @@ bool DomainServer::isPacketVerified(const udt::Packet& packet) { } } else { HIFI_FDEBUG("Packet of type" << headerType - << "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceNode->getUUID())); + << "received from unknown node with Local ID" << localSourceID); return false; } } diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml index 3726d3f260..5ddd97f3f6 100644 --- a/interface/resources/qml/controls-uit/Slider.qml +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -24,6 +24,7 @@ Slider { property alias minimumValue: slider.from property alias maximumValue: slider.to + property bool tickmarksEnabled: false height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control. y: sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0 diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml index 83c30ce162..9d63122dbc 100644 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -20,6 +20,7 @@ SpinBox { property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light property string label: "" + property string suffix: "" property string labelInside: "" property color colorLabelInside: hifi.colors.white property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0) @@ -34,8 +35,11 @@ SpinBox { property real realTo: 100.0 property real realStepSize: 1.0 + signal editingFinished() + implicitHeight: height implicitWidth: width + editable: true padding: 0 leftPadding: 0 @@ -68,16 +72,16 @@ SpinBox { } validator: DoubleValidator { - bottom: Math.min(spinBox.from, spinBox.to)*spinBox.factor - top: Math.max(spinBox.from, spinBox.to)*spinBox.factor + bottom: Math.min(spinBox.from, spinBox.to) + top: Math.max(spinBox.from, spinBox.to) } textFromValue: function(value, locale) { - return parseFloat(value*1.0/factor).toFixed(decimals); + return parseFloat(value/factor).toFixed(decimals); } valueFromText: function(text, locale) { - return Number.fromLocaleString(locale, text); + return Number.fromLocaleString(locale, text)*factor; } @@ -88,12 +92,14 @@ SpinBox { : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight - text: spinBox.textFromValue(spinBox.value, spinBox.locale) + text: spinBox.textFromValue(spinBox.value, spinBox.locale) + suffix verticalAlignment: Qt.AlignVCenter leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding //rightPadding: hifi.dimensions.spinnerSize width: spinBox.width - hifi.dimensions.spinnerSize + onEditingFinished: spinBox.editingFinished() } + up.indicator: Item { x: spinBox.width - implicitWidth - 5 y: 1 diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 8e7db44b7d..943f15e1de 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -21,6 +21,7 @@ Item { signal newViewRequestedCallback(var request) signal loadingChangedCallback(var loadRequest) + width: parent.width property bool interactive: false @@ -29,6 +30,10 @@ Item { id: hifi } + function stop() { + webViewCore.stop(); + } + function unfocus() { webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) { console.log('unfocus completed: ', result); diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 931c64e1ef..71bf69fdc8 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -21,6 +21,10 @@ Item { property bool passwordField: false property alias flickable: webroot.interactive + function stop() { + webroot.stop(); + } + // FIXME - Keyboard HMD only: Make Interface either set keyboardRaised property directly in OffscreenQmlSurface // or provide HMDinfo object to QML in RenderableWebEntityItem and do the following. /* diff --git a/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml b/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml index b1b6de4644..aadd7c88ae 100644 --- a/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml @@ -254,7 +254,7 @@ ModalWindow { text: root.warning; wrapMode: Text.WordWrap; font.italic: true; - maximumLineCount: 2; + maximumLineCount: 3; } HiFiGlyphs { diff --git a/interface/resources/qml/dialogs/CustomQueryDialog.qml b/interface/resources/qml/dialogs/CustomQueryDialog.qml index 008ed5b860..0c86b93c4b 100644 --- a/interface/resources/qml/dialogs/CustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/CustomQueryDialog.qml @@ -254,7 +254,7 @@ ModalWindow { text: root.warning; wrapMode: Text.WordWrap; font.italic: true; - maximumLineCount: 2; + maximumLineCount: 3; } HiFiGlyphs { diff --git a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml index 623388e9b3..81a2c5c1e0 100644 --- a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml @@ -282,7 +282,7 @@ TabletModalWindow { text: root.warning; wrapMode: Text.WordWrap; font.italic: true; - maximumLineCount: 2; + maximumLineCount: 3; } HiFiGlyphs { diff --git a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml index e670cd37c4..89e1096a04 100644 --- a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml @@ -18,11 +18,11 @@ Preference { height: control.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: { - spinner.value = preference.value; + spinner.realValue = preference.value; } function save() { - preference.value = spinner.value; + preference.value = spinner.realValue; preference.save(); } diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 3cba67bc82..731acc7e5b 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -21,7 +21,7 @@ Preference { Component.onCompleted: { slider.value = preference.value; - spinner.value = preference.value; + spinner.realValue = preference.value; } function save() { @@ -60,7 +60,7 @@ Preference { maximumValue: MyAvatar.getDomainMaxScale() stepSize: preference.step onValueChanged: { - spinner.value = value + spinner.realValue = value } anchors { right: spinner.left @@ -73,12 +73,12 @@ Preference { SpinBox { id: spinner decimals: preference.decimals - value: preference.value + realValue: preference.value minimumValue: MyAvatar.getDomainMinScale() maximumValue: MyAvatar.getDomainMaxScale() width: 100 onValueChanged: { - slider.value = value; + slider.value = realValue; } anchors { right: button.left @@ -92,10 +92,10 @@ Preference { id: button onClicked: { if (spinner.maximumValue >= 1) { - spinner.value = 1 + spinner.realValue = 1 slider.value = 1 } else { - spinner.value = spinner.maximumValue + spinner.realValue = spinner.maximumValue slider.value = spinner.maximumValue } } @@ -108,4 +108,4 @@ Preference { colorScheme: hifi.colorSchemes.dark } } -} \ No newline at end of file +} diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 8bf3a22338..c7c72e5f7c 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -258,7 +258,9 @@ Item { anchors.topMargin: 26; anchors.left: parent.left; anchors.leftMargin: 20; - width: paintedWidth; + anchors.right: parent.right; + anchors.rightMargin: 20; + elide: Text.ElideRight; height: 30; // Text size size: 22; @@ -844,7 +846,7 @@ Item { property string selectedRecipientUserName; property string selectedRecipientProfilePic; - visible: root.currentActiveView === "sendAssetStep"; + visible: root.currentActiveView === "sendAssetStep" || paymentSuccess.visible || paymentFailure.visible; anchors.fill: parent; anchors.topMargin: root.parentAppTitleBarHeight; @@ -856,7 +858,9 @@ Item { anchors.topMargin: 26; anchors.left: parent.left; anchors.leftMargin: 20; - width: paintedWidth; + anchors.right: parent.right; + anchors.rightMargin: 20; + elide: Text.ElideRight; height: 30; // Text size size: 22; @@ -907,7 +911,7 @@ Item { // "CHANGE" button HifiControlsUit.Button { id: changeButton; - color: root.assetName === "" ? hifi.buttons.none : hifi.buttons.noneBorderlessGray; + color: root.assetName === "" ? hifi.buttons.none : hifi.buttons.white; colorScheme: hifi.colorSchemes.dark; anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; @@ -1238,7 +1242,7 @@ Item { // Sending Asset Overlay START Rectangle { id: sendingAssetOverlay; - z: 998; + z: 999; visible: root.isCurrentlySendingAsset; anchors.fill: parent; @@ -1281,26 +1285,43 @@ Item { // Payment Success BEGIN Rectangle { id: paymentSuccess; + z: 998; visible: root.currentActiveView === "paymentSuccess"; anchors.fill: parent; color: Qt.rgba(0.0, 0.0, 0.0, 0.8); + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + Rectangle { - anchors.centerIn: parent; - width: parent.width - 30; - height: parent.height - 30; + anchors.top: parent.top; + anchors.topMargin: root.assetName === "" ? 15 : 150; + anchors.left: parent.left; + anchors.leftMargin: root.assetName === "" ? 15 : 50; + anchors.right: parent.right; + anchors.rightMargin: root.assetName === "" ? 15 : 50; + anchors.bottom: parent.bottom; + anchors.bottomMargin: root.assetName === "" ? 15 : 240; color: "#FFFFFF"; RalewaySemiBold { id: paymentSentText; - text: root.assetName === "" ? "Payment Sent" : '"' + root.assetName + '"'; + text: root.assetName === "" ? "Payment Sent" : "Gift Sent"; // Anchors anchors.top: parent.top; anchors.topMargin: 26; anchors.left: parent.left; anchors.leftMargin: 20; - width: paintedWidth; + anchors.right: parent.right; + anchors.rightMargin: 20; + elide: Text.ElideRight; height: 30; // Text size size: 22; @@ -1310,6 +1331,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_paymentSuccess; + visible: root.assetName === ""; text: hifi.glyphs.close; color: hifi.colors.lightGrayText; size: 26; @@ -1375,6 +1397,49 @@ Item { isDisplayingNearby: sendAssetStep.referrer === "nearby"; } } + + + Item { + id: giftContainer_paymentSuccess; + visible: root.assetName !== ""; + anchors.top: sendToContainer_paymentSuccess.bottom; + anchors.topMargin: 8; + anchors.left: parent.left; + anchors.leftMargin: 20; + anchors.right: parent.right; + anchors.rightMargin: 20; + height: 30; + + RalewaySemiBold { + id: gift_paymentSuccess; + text: "Gift:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + text: root.assetName; + // Anchors + anchors.top: parent.top; + anchors.left: gift_paymentSuccess.right; + anchors.right: parent.right; + height: parent.height; + // Text size + size: 18; + // Style + elide: Text.ElideRight; + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + } + } Item { id: amountContainer_paymentSuccess; @@ -1433,6 +1498,7 @@ Item { RalewaySemiBold { id: optionalMessage_paymentSuccess; + visible: root.assetName === ""; text: optionalMessage.text; // Anchors anchors.top: amountContainer_paymentSuccess.visible ? amountContainer_paymentSuccess.bottom : sendToContainer_paymentSuccess.bottom; @@ -1457,7 +1523,7 @@ Item { colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light; anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom; - anchors.bottomMargin: 80; + anchors.bottomMargin: root.assetName === "" ? 80 : 30; height: 50; width: 120; text: "Close"; @@ -1476,26 +1542,43 @@ Item { // Payment Failure BEGIN Rectangle { id: paymentFailure; + z: 998; visible: root.currentActiveView === "paymentFailure"; anchors.fill: parent; color: Qt.rgba(0.0, 0.0, 0.0, 0.8); + // This object is always used in a popup or full-screen Wallet section. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup/section. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + Rectangle { - anchors.centerIn: parent; - width: parent.width - 30; - height: parent.height - 30; + anchors.top: parent.top; + anchors.topMargin: root.assetName === "" ? 15 : 150; + anchors.left: parent.left; + anchors.leftMargin: root.assetName === "" ? 15 : 50; + anchors.right: parent.right; + anchors.rightMargin: root.assetName === "" ? 15 : 50; + anchors.bottom: parent.bottom; + anchors.bottomMargin: root.assetName === "" ? 15 : 300; color: "#FFFFFF"; RalewaySemiBold { id: paymentFailureText; - text: root.assetName === "" ? "Payment Failed" : '"' + root.assetName + '"'; + text: root.assetName === "" ? "Payment Failed" : "Failed"; // Anchors anchors.top: parent.top; anchors.topMargin: 26; anchors.left: parent.left; anchors.leftMargin: 20; - width: paintedWidth; + anchors.right: parent.right; + anchors.rightMargin: 20; + elide: Text.ElideRight; height: 30; // Text size size: 22; @@ -1505,6 +1588,7 @@ Item { HiFiGlyphs { id: closeGlyphButton_paymentFailure; + visible: root.assetName === ""; text: hifi.glyphs.close; color: hifi.colors.lightGrayText; size: 26; @@ -1551,6 +1635,7 @@ Item { Item { id: sendToContainer_paymentFailure; + visible: root.assetName === ""; anchors.top: paymentFailureDetailText.bottom; anchors.topMargin: 8; anchors.left: parent.left; @@ -1645,7 +1730,8 @@ Item { } RalewaySemiBold { - id: optionalMessage_paymentFailuire; + id: optionalMessage_paymentFailure; + visible: root.assetName === ""; text: optionalMessage.text; // Anchors anchors.top: amountContainer_paymentFailure.visible ? amountContainer_paymentFailure.bottom : sendToContainer_paymentFailure.bottom; @@ -1663,14 +1749,15 @@ Item { verticalAlignment: Text.AlignTop; } - // "Close" button + // "Cancel" button HifiControlsUit.Button { id: closeButton_paymentFailure; color: hifi.buttons.noneBorderless; colorScheme: root.assetName === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light; - anchors.horizontalCenter: parent.horizontalCenter; + anchors.right: retryButton_paymentFailure.left; + anchors.rightMargin: 12; anchors.bottom: parent.bottom; - anchors.bottomMargin: 80; + anchors.bottomMargin: root.assetName === "" ? 80 : 30; height: 50; width: 120; text: "Cancel"; @@ -1691,7 +1778,7 @@ Item { anchors.right: parent.right; anchors.rightMargin: 12; anchors.bottom: parent.bottom; - anchors.bottomMargin: 80; + anchors.bottomMargin: root.assetName === "" ? 80 : 30; height: 50; width: 120; text: "Retry"; @@ -1768,7 +1855,7 @@ Item { switch (message.method) { case 'selectRecipient': if (message.isSelected) { - chooseRecipientNearby.selectedRecipient = message.id[0]; + chooseRecipientNearby.selectedRecipient = message.id; sendAssetStep.selectedRecipientDisplayName = message.displayName; sendAssetStep.selectedRecipientUserName = message.userName; } else { diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 7d7a882ee0..4db98091c1 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -239,7 +239,6 @@ Item { width: 62; onLoaded: { - item.enabled = (root.purchaseStatus === "confirmed"); item.buttonGlyphText = hifi.glyphs.gift; item.buttonText = "Gift"; item.buttonClicked = function() { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 08e3e7a552..6b48f8d51d 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -124,6 +124,14 @@ Rectangle { root.numUpdatesAvailable = result.data.updates.length; } } + + onAppInstalled: { + root.installedApps = Commerce.getInstalledApps(); + } + + onAppUninstalled: { + root.installedApps = Commerce.getInstalledApps(); + } } Timer { @@ -249,6 +257,145 @@ Rectangle { Commerce.getWalletStatus(); } } + + Item { + id: installedAppsContainer; + z: 998; + visible: false; + anchors.top: titleBarContainer.bottom; + anchors.topMargin: -titleBarContainer.additionalDropdownHeight; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: parent.width; + + RalewayRegular { + id: installedAppsHeader; + anchors.top: parent.top; + anchors.topMargin: 10; + anchors.left: parent.left; + anchors.leftMargin: 12; + height: 80; + width: paintedWidth; + text: "All Installed Marketplace Apps"; + color: hifi.colors.black; + size: 22; + } + + ListView { + id: installedAppsList; + clip: true; + model: installedAppsModel; + snapMode: ListView.SnapToItem; + // Anchors + anchors.top: installedAppsHeader.bottom; + anchors.left: parent.left; + anchors.bottom: sideloadAppButton.top; + width: parent.width; + delegate: Item { + width: parent.width; + height: 40; + + RalewayRegular { + text: model.appUrl; + // Text size + size: 16; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 12; + height: parent.height; + anchors.right: sideloadAppOpenButton.left; + anchors.rightMargin: 8; + elide: Text.ElideRight; + // Style + color: hifi.colors.black; + // Alignment + verticalAlignment: Text.AlignVCenter; + + MouseArea { + anchors.fill: parent; + onClicked: { + Window.copyToClipboard((model.appUrl).slice(0, -9)); + } + } + } + + HifiControlsUit.Button { + id: sideloadAppOpenButton; + text: "OPEN"; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 2; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 2; + anchors.right: uninstallGlyph.left; + anchors.rightMargin: 8; + width: 80; + onClicked: { + Commerce.openApp(model.appUrl); + } + } + + HiFiGlyphs { + id: uninstallGlyph; + text: hifi.glyphs.close; + color: hifi.colors.black; + size: 22; + anchors.top: parent.top; + anchors.right: parent.right; + anchors.rightMargin: 6; + width: 35; + height: parent.height; + horizontalAlignment: Text.AlignHCenter; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + onExited: { + parent.text = hifi.glyphs.close; + } + onClicked: { + Commerce.uninstallApp(model.appUrl); + } + } + } + } + } + HifiControlsUit.Button { + id: sideloadAppButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.right: closeAppListButton.left; + anchors.rightMargin: 8; + height: 40; + text: "SIDELOAD APP FROM LOCAL DISK"; + onClicked: { + Window.browseChanged.connect(onFileOpenChanged); + Window.browseAsync("Locate your app's .app.json file", "", "*.app.json"); + } + } + HifiControlsUit.Button { + id: closeAppListButton; + color: hifi.buttons.white; + colorScheme: hifi.colorSchemes.dark; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + anchors.right: parent.right; + anchors.rightMargin: 8; + width: 100; + height: 40; + text: "BACK"; + onClicked: { + installedAppsContainer.visible = false; + } + } + } HifiWallet.NeedsLogIn { id: needsLogIn; @@ -317,7 +464,7 @@ Rectangle { // Item { id: purchasesContentsContainer; - visible: root.activeView === "purchasesMain"; + visible: root.activeView === "purchasesMain" && !installedAppsList.visible; // Anchors anchors.left: parent.left; anchors.right: parent.right; @@ -959,6 +1106,39 @@ Rectangle { } } + Keys.onPressed: { + if ((event.key == Qt.Key_F) && (event.modifiers & Qt.ControlModifier)) { + installedAppsContainer.visible = !installedAppsContainer.visible; + console.log("User changed visibility of installedAppsContainer to " + installedAppsContainer.visible); + } + } + function onFileOpenChanged(filename) { + // disconnect the event, otherwise the requests will stack up + try { // Not all calls to onFileOpenChanged() connect an event. + Window.browseChanged.disconnect(onFileOpenChanged); + } catch (e) { + console.log('Purchases.qml ignoring', e); + } + if (filename) { + Commerce.installApp(filename); + } + } + ListModel { + id: installedAppsModel; + } + onInstalledAppsChanged: { + installedAppsModel.clear(); + var installedAppsArray = root.installedApps.split(","); + var installedAppsObject = []; + // "- 1" because the last app string ends with "," + for (var i = 0; i < installedAppsArray.length - 1; i++) { + installedAppsObject[i] = { + "appUrl": installedAppsArray[i] + } + } + installedAppsModel.append(installedAppsObject); + } + // // Function Name: fromScript() // diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml index d93e077b5a..30e03bd02e 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -181,11 +181,11 @@ Item { minimumValue: 0.01 maximumValue: 10 realStepSize: 0.05; - value: attachment ? attachment.scale : 1.0 + realValue: attachment ? attachment.scale : 1.0 colorScheme: hifi.colorSchemes.dark - onValueChanged: { - if (completed && attachment && attachment.scale !== value) { - attachment.scale = value; + onRealValueChanged: { + if (completed && attachment && attachment.scale !== realValue) { + attachment.scale = realValue; updateAttachment(); } } diff --git a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml index 228d71fe6f..311492858b 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml @@ -51,7 +51,7 @@ Item { id: xspinner width: root.spinboxWidth anchors { left: parent.left } - value: root.vector.x + realValue: root.vector.x labelInside: "X:" colorScheme: hifi.colorSchemes.dark colorLabelInside: hifi.colors.redHighlight @@ -72,17 +72,17 @@ Item { id: yspinner width: root.spinboxWidth anchors { horizontalCenter: parent.horizontalCenter } - value: root.vector.y + realValue: root.vector.y labelInside: "Y:" colorLabelInside: hifi.colors.greenHighlight colorScheme: hifi.colorSchemes.dark decimals: root.decimals - stepSize: root.stepSize + realStepSize: root.stepSize maximumValue: root.maximumValue minimumValue: root.minimumValue - onValueChanged: { - if (value !== vector.y) { - vector.y = value + onRealValueChanged: { + if (realValue !== vector.y) { + vector.y = realValue root.valueChanged(); } } @@ -93,17 +93,17 @@ Item { id: zspinner width: root.spinboxWidth anchors { right: parent.right; } - value: root.vector.z + realValue: root.vector.z labelInside: "Z:" colorLabelInside: hifi.colors.primaryHighlight colorScheme: hifi.colorSchemes.dark decimals: root.decimals - stepSize: root.stepSize + realStepSize: root.stepSize maximumValue: root.maximumValue minimumValue: root.minimumValue - onValueChanged: { - if (value !== vector.z) { - vector.z = value + onRealValueChanged: { + if (realValue !== vector.z) { + vector.z = realValue root.valueChanged(); } } diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 3e1a7bf139..ffd3d81b84 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -17,6 +17,7 @@ StackView { id: stack initialItem: inputConfiguration property alias messageVisible: imageMessageBox.visible + property alias selectedPlugin: box.currentText Rectangle { id: inputConfiguration anchors.fill: parent diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index bd3a95bca0..8fb49dffc0 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -34,7 +34,7 @@ Rectangle { readonly property bool hmdHead: headBox.checked readonly property bool headPuck: headPuckBox.checked readonly property bool handController: handBox.checked - + readonly property bool handPuck: handPuckBox.checked readonly property bool hmdDesktop: hmdInDesktop.checked @@ -105,7 +105,7 @@ Rectangle { RalewayBold { size: 12 - text: "Vive HMD" + text: stack.selectedPlugin + " HMD" color: hifi.colors.lightGrayText } @@ -143,7 +143,7 @@ Rectangle { anchors.topMargin: 5 anchors.left: openVrConfiguration.left anchors.leftMargin: leftMargin + 10 - + onClicked: { if (checked) { headBox.checked = false; @@ -178,8 +178,8 @@ Rectangle { label: "Y Offset" suffix: " cm" minimumValue: -10 - stepSize: 1 - value: -5 + realStepSize: 1 + realValue: -5 colorScheme: hifi.colorSchemes.dark onEditingFinished: { @@ -193,10 +193,10 @@ Rectangle { width: 112 label: "Z Offset" minimumValue: -10 - stepSize: 1 + realStepSize: 1 decimals: 1 suffix: " cm" - value: -5 + realValue: -5 colorScheme: hifi.colorSchemes.dark onEditingFinished: { @@ -288,7 +288,7 @@ Rectangle { suffix: " cm" label: "Y Offset" minimumValue: -10 - stepSize: 1 + realStepSize: 1 colorScheme: hifi.colorSchemes.dark onEditingFinished: { @@ -303,7 +303,7 @@ Rectangle { label: "Z Offset" suffix: " cm" minimumValue: -10 - stepSize: 1 + realStepSize: 1 decimals: 1 colorScheme: hifi.colorSchemes.dark @@ -535,9 +535,9 @@ Rectangle { suffix: " cm" label: "Arm Circumference" minimumValue: 0 - stepSize: 1.0 + realStepSize: 1.0 colorScheme: hifi.colorSchemes.dark - value: 33.0 + realValue: 33.0 onEditingFinished: { sendConfigurationSettings(); @@ -550,10 +550,10 @@ Rectangle { label: "Shoulder Width" suffix: " cm" minimumValue: 0 - stepSize: 1.0 + realStepSize: 1.0 decimals: 1 colorScheme: hifi.colorSchemes.dark - value: 48 + realValue: 48 onEditingFinished: { sendConfigurationSettings(); @@ -659,13 +659,13 @@ Rectangle { InputConfiguration.uncalibratePlugin(pluginName); updateCalibrationButton(); } else { - calibrationTimer.interval = timeToCalibrate.value * 1000 - openVrConfiguration.countDown = timeToCalibrate.value; + calibrationTimer.interval = timeToCalibrate.realValue * 1000 + openVrConfiguration.countDown = timeToCalibrate.realValue; var calibratingScreen = screen.createObject(); stack.push(calibratingScreen); calibratingScreen.canceled.connect(cancelCalibration); calibratingScreen.restart.connect(restartCalibration); - calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.value); + calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.realValue); calibrationTimer.start(); } } @@ -728,12 +728,12 @@ Rectangle { anchors.leftMargin: leftMargin minimumValue: 5 - value: 5 + realValue: 5 colorScheme: hifi.colorSchemes.dark onEditingFinished: { - calibrationTimer.interval = value * 1000; - openVrConfiguration.countDown = value; + calibrationTimer.interval = realValue * 1000; + openVrConfiguration.countDown = realValue; numberAnimation.duration = calibrationTimer.interval; } } @@ -772,12 +772,12 @@ Rectangle { RalewayBold { id: advanceSettings - + text: "Advanced Settings" size: 12 - + color: hifi.colors.white - + anchors.top: advanceSeperator.bottom anchors.topMargin: 10 anchors.left: parent.left @@ -795,7 +795,7 @@ Rectangle { anchors.topMargin: 5 anchors.left: openVrConfiguration.left anchors.leftMargin: leftMargin + 10 - + onClicked: { if (!checked & hmdInDesktop.checked) { headBox.checked = true; @@ -809,9 +809,9 @@ Rectangle { RalewayBold { id: viveDesktopText size: 10 - text: "Use Vive devices in desktop mode" + text: "Use " + stack.selectedPlugin + " devices in desktop mode" color: hifi.colors.white - + anchors { left: viveInDesktop.right leftMargin: 5 @@ -819,7 +819,7 @@ Rectangle { } } - + NumberAnimation { id: numberAnimation target: openVrConfiguration @@ -910,8 +910,8 @@ Rectangle { var desktopMode = settings["desktopMode"]; var hmdDesktopPosition = settings["hmdDesktopTracking"]; - armCircumference.value = settings.armCircumference; - shoulderWidth.value = settings.shoulderWidth; + armCircumference.realValue = settings.armCircumference; + shoulderWidth.realValue = settings.shoulderWidth; if (HmdHead) { headBox.checked = true; @@ -1075,22 +1075,22 @@ Rectangle { var headObject = { "override": overrideHead, - "Y": headYOffset.value, - "Z": headZOffset.value + "Y": headYOffset.realValue, + "Z": headZOffset.realValue } var handObject = { "override": overrideHandController, - "Y": handYOffset.value, - "Z": handZOffset.value + "Y": handYOffset.realValue, + "Z": handZOffset.realValue } var settingsObject = { "bodyConfiguration": trackerConfiguration, "headConfiguration": headObject, "handConfiguration": handObject, - "armCircumference": armCircumference.value, - "shoulderWidth": shoulderWidth.value, + "armCircumference": armCircumference.realValue, + "shoulderWidth": shoulderWidth.realValue, "desktopMode": viveInDesktop.checked, "hmdDesktopTracking": hmdInDesktop.checked } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 454848c87d..9717bb25c2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -145,6 +145,16 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "AudioClient.h" #include "audio/AudioScope.h" #include "avatar/AvatarManager.h" @@ -1061,6 +1071,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (steamClient) { qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID(); } + setCrashAnnotation("steam", property(hifi::properties::STEAM).toBool() ? "1" : "0"); + qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); qCDebug(interfaceapp) << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION; qCDebug(interfaceapp) << "[VERSION] VERSION:" << BuildInfo::VERSION; @@ -1146,6 +1158,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl))); + connect(&domainHandler, &DomainHandler::domainURLChanged, [](QUrl domainURL){ + setCrashAnnotation("domain", domainURL.toString().toStdString()); + }); connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); @@ -1191,6 +1206,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo auto dialogsManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); + connect(accountManager.data(), &AccountManager::usernameChanged, [](QString username){ + setCrashAnnotation("username", username.toStdString()); + }); // set the account manager's root URL and trigger a login request if we don't have the access token accountManager->setIsAgent(true); @@ -1208,6 +1226,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount); connect(this, &Application::activeDisplayPluginChanged, this, [](){ qApp->setProperty(hifi::properties::HMD, qApp->isHMDMode()); + auto displayPlugin = qApp->getActiveDisplayPlugin(); + setCrashAnnotation("display_plugin", displayPlugin->getName().toStdString()); + setCrashAnnotation("hmd", displayPlugin->isHmd() ? "1" : "0"); }); connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode); @@ -1215,6 +1236,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(myAvatar.get(), &MyAvatar::positionGoneTo, DependencyManager::get().data(), &AddressManager::storeCurrentAddress); + connect(myAvatar.get(), &MyAvatar::skeletonModelURLChanged, [](){ + QUrl avatarURL = qApp->getMyAvatar()->getSkeletonModelURL(); + setCrashAnnotation("avatar", avatarURL.toString().toStdString()); + }); + + // Inititalize sample before registering _sampleSound = DependencyManager::get()->getSound(PathUtils::resourcesUrl("sounds/sample.wav")); @@ -1307,6 +1334,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Needs to happen AFTER the render engine initialization to access its configuration initializeUi(); + updateVerboseLogging(); + init(); qCDebug(interfaceapp, "init() complete."); @@ -1323,49 +1352,48 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Make sure we don't time out during slow operations at startup updateHeartbeat(); - // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. - // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. - static const QString TESTER = "HIFI_TESTER"; - auto gpuIdent = GPUIdent::getInstance(); - auto glContextData = getGLContextData(); - QJsonObject properties = { - { "version", applicationVersion() }, - { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) }, - { "previousSessionCrashed", _previousSessionCrashed }, - { "previousSessionRuntime", sessionRunTime.get() }, - { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, - { "kernel_type", QSysInfo::kernelType() }, - { "kernel_version", QSysInfo::kernelVersion() }, - { "os_type", QSysInfo::productType() }, - { "os_version", QSysInfo::productVersion() }, - { "gpu_name", gpuIdent->getName() }, - { "gpu_driver", gpuIdent->getDriver() }, - { "gpu_memory", static_cast(gpuIdent->getMemory()) }, - { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) }, - { "gl_version", glContextData["version"] }, - { "gl_vender", glContextData["vendor"] }, - { "gl_sl_version", glContextData["sl_version"] }, - { "gl_renderer", glContextData["renderer"] }, - { "ideal_thread_count", QThread::idealThreadCount() } - }; - auto macVersion = QSysInfo::macVersion(); - if (macVersion != QSysInfo::MV_None) { - properties["os_osx_version"] = QSysInfo::macVersion(); - } - auto windowsVersion = QSysInfo::windowsVersion(); - if (windowsVersion != QSysInfo::WV_None) { - properties["os_win_version"] = QSysInfo::windowsVersion(); + constexpr auto INSTALLER_INI_NAME = "installer.ini"; + auto iniPath = QDir(applicationDirPath()).filePath(INSTALLER_INI_NAME); + QFile installerFile { iniPath }; + std::unordered_map installerKeyValues; + if (installerFile.open(QIODevice::ReadOnly)) { + while (!installerFile.atEnd()) { + auto line = installerFile.readLine(); + if (!line.isEmpty()) { + auto index = line.indexOf("="); + if (index >= 0) { + installerKeyValues[line.mid(0, index).trimmed()] = line.mid(index + 1).trimmed(); + } + } + } } - ProcessorInfo procInfo; - if (getProcessorInfo(procInfo)) { - properties["processor_core_count"] = procInfo.numProcessorCores; - properties["logical_processor_count"] = procInfo.numLogicalProcessors; - properties["processor_l1_cache_count"] = procInfo.numProcessorCachesL1; - properties["processor_l2_cache_count"] = procInfo.numProcessorCachesL2; - properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3; + // In practice we shouldn't run across installs that don't have a known installer type. + // Client or Client+Server installs should always have the installer.ini next to their + // respective interface.exe, and Steam installs will be detected as such. If a user were + // to delete the installer.ini, though, and as an example, we won't know the context of the + // original install. + constexpr auto INSTALLER_KEY_TYPE = "type"; + constexpr auto INSTALLER_KEY_CAMPAIGN = "campaign"; + constexpr auto INSTALLER_TYPE_UNKNOWN = "unknown"; + constexpr auto INSTALLER_TYPE_STEAM = "steam"; + + auto typeIt = installerKeyValues.find(INSTALLER_KEY_TYPE); + QString installerType = INSTALLER_TYPE_UNKNOWN; + if (typeIt == installerKeyValues.end()) { + if (property(hifi::properties::STEAM).toBool()) { + installerType = INSTALLER_TYPE_STEAM; + } + } else { + installerType = typeIt->second; } + auto campaignIt = installerKeyValues.find(INSTALLER_KEY_CAMPAIGN); + QString installerCampaign = campaignIt != installerKeyValues.end() ? campaignIt->second : ""; + + qDebug() << "Detected installer type:" << installerType; + qDebug() << "Detected installer campaign:" << installerCampaign; + // add firstRun flag from settings to launch event Setting::Handle firstRun { Settings::firstRun, true }; @@ -1378,6 +1406,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo userActivityLogger.disable(false); } + QString machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + if (userActivityLogger.isEnabled()) { // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. @@ -1387,6 +1417,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QJsonObject properties = { { "version", applicationVersion() }, { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) }, + { "installer_campaign", installerCampaign }, + { "installer_type", installerType }, { "previousSessionCrashed", _previousSessionCrashed }, { "previousSessionRuntime", sessionRunTime.get() }, { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, @@ -1425,11 +1457,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo properties["first_run"] = firstRun.get(); // add the user's machine ID to the launch event - properties["machine_fingerprint"] = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + properties["machine_fingerprint"] = machineFingerPrint; userActivityLogger.logAction("launch", properties); } + setCrashAnnotation("machine_fingerprint", machineFingerPrint.toStdString()); + _entityEditSender.setMyAvatar(myAvatar.get()); // The entity octree will have to know about MyAvatar for the parentJointName import @@ -1706,7 +1740,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater"; bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1; if (!noUpdater) { + constexpr auto INSTALLER_TYPE_CLIENT_ONLY = "client_only"; + auto applicationUpdater = DependencyManager::get(); + + AutoUpdater::InstallerType type = installerType == INSTALLER_TYPE_CLIENT_ONLY + ? AutoUpdater::InstallerType::CLIENT_ONLY : AutoUpdater::InstallerType::FULL; + + applicationUpdater->setInstallerType(type); + applicationUpdater->setInstallerCampaign(installerCampaign); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); applicationUpdater->checkForUpdate(); } @@ -2170,6 +2212,46 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo #endif } +void Application::updateVerboseLogging() { + bool enable = Menu::getInstance()->isOptionChecked(MenuOption::VerboseLogging); + + const_cast(&animation())->setEnabled(QtDebugMsg, enable); + const_cast(&animation())->setEnabled(QtInfoMsg, enable); + + const_cast(&avatars())->setEnabled(QtDebugMsg, enable); + const_cast(&avatars())->setEnabled(QtInfoMsg, enable); + + const_cast(&scriptengine())->setEnabled(QtDebugMsg, enable); + const_cast(&scriptengine())->setEnabled(QtInfoMsg, enable); + + const_cast(&modelformat())->setEnabled(QtDebugMsg, enable); + const_cast(&modelformat())->setEnabled(QtInfoMsg, enable); + + const_cast(&controllers())->setEnabled(QtDebugMsg, enable); + const_cast(&controllers())->setEnabled(QtInfoMsg, enable); + + const_cast(&resourceLog())->setEnabled(QtDebugMsg, enable); + const_cast(&resourceLog())->setEnabled(QtInfoMsg, enable); + + const_cast(&networking())->setEnabled(QtDebugMsg, enable); + const_cast(&networking())->setEnabled(QtInfoMsg, enable); + + const_cast(&asset_client())->setEnabled(QtDebugMsg, enable); + const_cast(&asset_client())->setEnabled(QtInfoMsg, enable); + + const_cast(&messages_client())->setEnabled(QtDebugMsg, enable); + const_cast(&messages_client())->setEnabled(QtInfoMsg, enable); + + const_cast(&storagelogging())->setEnabled(QtDebugMsg, enable); + const_cast(&storagelogging())->setEnabled(QtInfoMsg, enable); + + const_cast(&uiLogging())->setEnabled(QtDebugMsg, enable); + const_cast(&uiLogging())->setEnabled(QtInfoMsg, enable); + + const_cast(&glLogging())->setEnabled(QtDebugMsg, enable); + const_cast(&glLogging())->setEnabled(QtInfoMsg, enable); +} + void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { DomainHandler::ConnectionRefusedReason reasonCode = static_cast(reasonCodeInt); @@ -3041,7 +3123,6 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { PROFILE_RANGE(render, __FUNCTION__); bool sandboxIsRunning = SandboxUtils::readStatus(reply->readAll()); - qDebug() << "HandleSandboxStatus" << sandboxIsRunning; enum HandControllerType { Vive, @@ -4758,7 +4839,7 @@ void Application::updateLOD(float deltaTime) const { } } -void Application::pushPostUpdateLambda(void* key, std::function func) { +void Application::pushPostUpdateLambda(void* key, const std::function& func) { std::unique_lock guard(_postUpdateLambdasLock); _postUpdateLambdas[key] = func; } @@ -7368,7 +7449,7 @@ void Application::windowMinimizedChanged(bool minimized) { } } -void Application::postLambdaEvent(std::function f) { +void Application::postLambdaEvent(const std::function& f) { if (this->thread() == QThread::currentThread()) { f(); } else { @@ -7376,6 +7457,15 @@ void Application::postLambdaEvent(std::function f) { } } +void Application::sendLambdaEvent(const std::function& f) { + if (this->thread() == QThread::currentThread()) { + f(); + } else { + LambdaEvent event(f); + QCoreApplication::sendEvent(this, &event); + } +} + void Application::initPlugins(const QStringList& arguments) { QCommandLineOption display("display", "Preferred displays", "displays"); QCommandLineOption disableDisplays("disable-displays", "Displays to disable", "displays"); diff --git a/interface/src/Application.h b/interface/src/Application.h index 50812a00ec..998543d1d0 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -136,7 +136,8 @@ public: Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runningMarkerExisted); ~Application(); - void postLambdaEvent(std::function f) override; + void postLambdaEvent(const std::function& f) override; + void sendLambdaEvent(const std::function& f) override; QString getPreviousScriptLocation(); void setPreviousScriptLocation(const QString& previousScriptLocation); @@ -240,7 +241,7 @@ public: qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } - bool isAboutToQuit() const override { return _aboutToQuit; } + bool isAboutToQuit() const { return _aboutToQuit; } bool isPhysicsEnabled() const { return _physicsEnabled; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display @@ -264,7 +265,7 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } - virtual void pushPostUpdateLambda(void* key, std::function func) override; + virtual void pushPostUpdateLambda(void* key, const std::function& func) override; void updateMyAvatarLookAtPosition(); @@ -403,6 +404,7 @@ public slots: Q_INVOKABLE bool askBeforeSetAvatarUrl(const QString& avatarUrl) { return askToSetAvatarUrl(avatarUrl); } + void updateVerboseLogging(); Q_INVOKABLE void openAndroidActivity(const QString& activityName); Q_INVOKABLE void performHapticFeedback(const QString& feedbackConstant); diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index 7e2f64379e..177e6e493e 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -15,6 +15,11 @@ #include #include "Bookmarks.h" +/**jsdoc + * This API helps manage adding and deleting avatar bookmarks. + * @namespace AvatarBookmarks + */ + class AvatarBookmarks: public Bookmarks, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -23,7 +28,12 @@ public: AvatarBookmarks(); void setupMenus(Menu* menubar, MenuWrapper* menu) override; + public slots: + /**jsdoc + * Add the current Avatar to your avatar bookmarks. + * @function AvatarBookmarks.addBookMark + */ void addBookmark(); protected: diff --git a/interface/src/Bookmarks.h b/interface/src/Bookmarks.h index dd47a286bf..7bd32ce7f1 100644 --- a/interface/src/Bookmarks.h +++ b/interface/src/Bookmarks.h @@ -1,4 +1,4 @@ -// + // // Bookmarks.h // interface/src // @@ -48,6 +48,9 @@ protected: bool _isMenuSorted; protected slots: + /**jsdoc + * @function AvatarBookmarks.deleteBookmark + */ void deleteBookmark(); private: diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index fcb1908994..8deddbda82 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -43,12 +43,10 @@ void ConnectionMonitor::init() { } void ConnectionMonitor::startTimer() { - qDebug() << "ConnectionMonitor: Starting timer"; _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); } void ConnectionMonitor::stopTimer() { - qDebug() << "ConnectionMonitor: Stopping timer"; _timer.stop(); DependencyManager::get()->setDomainConnectionFailureVisibility(false); } diff --git a/interface/src/Crashpad.cpp b/interface/src/Crashpad.cpp index 27da619af1..e39cd42d81 100644 --- a/interface/src/Crashpad.cpp +++ b/interface/src/Crashpad.cpp @@ -15,6 +15,8 @@ #if HAS_CRASHPAD +#include + #include #include @@ -23,8 +25,8 @@ #include #include #include -// #include -// #include +#include +#include using namespace crashpad; @@ -35,32 +37,19 @@ static std::wstring gIPCPipe; extern QString qAppFileName(); -// crashpad::AnnotationList* crashpadAnnotations { nullptr }; +std::mutex annotationMutex; +crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr }; #include LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { - static const DWORD EXTERNAL_EXCEPTION_CODE{ 0xe06d7363 }; - static const DWORD HEAP_CORRUPTION_CODE{ 0xc0000374 }; - - auto exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; - if (exceptionCode == EXTERNAL_EXCEPTION_CODE) { - return EXCEPTION_CONTINUE_SEARCH; - } - - if (exceptionCode == HEAP_CORRUPTION_CODE) { - qCritical() << "VectoredExceptionHandler: Heap corruption:" << QString::number(exceptionCode, 16); - + if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || + pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN) { CrashpadClient client; if (gIPCPipe.length()) { - bool rc = client.SetHandlerIPCPipe(gIPCPipe); - qCritical() << "SetHandlerIPCPipe = " << rc; - } else { - qCritical() << "No IPC Pipe was previously defined for crash handler."; + client.SetHandlerIPCPipe(gIPCPipe); } - qCritical() << "Calling DumpAndCrash()"; client.DumpAndCrash(pExceptionInfo); - return EXCEPTION_CONTINUE_SEARCH; } return EXCEPTION_CONTINUE_SEARCH; @@ -116,12 +105,14 @@ bool startCrashHandler() { } void setCrashAnnotation(std::string name, std::string value) { - // if (!crashpadAnnotations) { - // crashpadAnnotations = new crashpad::AnnotationList(); // don't free this, let it leak - // crashpad::CrashpadInfo* crashpad_info = crashpad::GetCrashpadInfo(); - // crashpad_info->set_simple_annotations(crashpadAnnotations); - // } - // crashpadAnnotations->SetKeyValue(name, value); + std::lock_guard guard(annotationMutex); + if (!crashpadAnnotations) { + crashpadAnnotations = new crashpad::SimpleStringDictionary(); // don't free this, let it leak + crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo(); + crashpad_info->set_simple_annotations(crashpadAnnotations); + } + std::replace(value.begin(), value.end(), ',', ';'); + crashpadAnnotations->SetKeyValue(name, value); } #else diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 33cfc481d7..b3c059de7f 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -20,6 +20,7 @@ #include #include +#include "Crashpad.h" #include "DiscoverabilityManager.h" #include "Menu.h" @@ -127,10 +128,12 @@ void DiscoverabilityManager::updateLocation() { QNetworkAccessManager::PutOperation, callbackParameters); } - // Update Steam + // Update Steam and crash logger + QUrl currentAddress = addressManager->currentFacingPublicAddress(); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { - steamClient->updateLocation(domainHandler.getHostname(), addressManager->currentFacingPublicAddress()); + steamClient->updateLocation(domainHandler.getHostname(), currentAddress); } + setCrashAnnotation("address", currentAddress.toString().toStdString()); } void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply) { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index a12f809efe..96d92e91e9 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -34,43 +34,127 @@ const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.04f; class AABox; +/**jsdoc + * The LOD class manages your Level of Detail functions within Interface. + * @namespace LODManager + * @property {number} presentTime Read-only. + * @property {number} engineRunTime Read-only. + * @property {number} gpuTime Read-only. + * @property {number} avgRenderTime Read-only. + * @property {number} fps Read-only. + * @property {number} lodLevel Read-only. + * @property {number} lodDecreaseFPS Read-only. + * @property {number} lodIncreaseFPS Read-only. + */ + class LODManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY -public: - Q_INVOKABLE void setAutomaticLODAdjust(bool value) { _automaticLODAdjust = value; } - Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; } - - Q_INVOKABLE void setDesktopLODDecreaseFPS(float value); - Q_INVOKABLE float getDesktopLODDecreaseFPS() const; - Q_INVOKABLE float getDesktopLODIncreaseFPS() const; - - Q_INVOKABLE void setHMDLODDecreaseFPS(float value); - Q_INVOKABLE float getHMDLODDecreaseFPS() const; - Q_INVOKABLE float getHMDLODIncreaseFPS() const; - - // User Tweakable LOD Items - Q_INVOKABLE QString getLODFeedbackText(); - Q_INVOKABLE void setOctreeSizeScale(float sizeScale); - Q_INVOKABLE float getOctreeSizeScale() const { return _octreeSizeScale; } - - Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust); - Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } - - Q_INVOKABLE float getLODDecreaseFPS() const; - Q_INVOKABLE float getLODIncreaseFPS() const; - Q_PROPERTY(float presentTime READ getPresentTime) Q_PROPERTY(float engineRunTime READ getEngineRunTime) Q_PROPERTY(float gpuTime READ getGPUTime) Q_PROPERTY(float avgRenderTime READ getAverageRenderTime) Q_PROPERTY(float fps READ getMaxTheoreticalFPS) Q_PROPERTY(float lodLevel READ getLODLevel) - Q_PROPERTY(float lodDecreaseFPS READ getLODDecreaseFPS) Q_PROPERTY(float lodIncreaseFPS READ getLODIncreaseFPS) +public: + + /**jsdoc + * @function LODManager.setAutomaticLODAdjust + * @param {boolean} value + */ + Q_INVOKABLE void setAutomaticLODAdjust(bool value) { _automaticLODAdjust = value; } + + /**jsdoc + * @function LODManager.getAutomaticLODAdjust + * @returns {boolean} + */ + Q_INVOKABLE bool getAutomaticLODAdjust() const { return _automaticLODAdjust; } + + /**jsdoc + * @function LODManager.setDesktopLODDecreaseFPS + * @param {number} value + */ + Q_INVOKABLE void setDesktopLODDecreaseFPS(float value); + + /**jsdoc + * @function LODManager.getDesktopLODDecreaseFPS + * @returns {number} + */ + + Q_INVOKABLE float getDesktopLODDecreaseFPS() const; + + /**jsdoc + * @function LODManager.getDesktopLODIncreaseFPS + * @returns {number} + */ + Q_INVOKABLE float getDesktopLODIncreaseFPS() const; + + /**jsdoc + * @function LODManager.setHMDLODDecreaseFPS + * @param {number} value + */ + + Q_INVOKABLE void setHMDLODDecreaseFPS(float value); + + /**jsdoc + * @function LODManager.getHMDLODDecreaseFPS + * @returns {number} + */ + Q_INVOKABLE float getHMDLODDecreaseFPS() const; + + /**jsdoc + * @function LODManager.getHMDLODIncreaseFPS + * @returns {number} + */ + Q_INVOKABLE float getHMDLODIncreaseFPS() const; + + // User Tweakable LOD Items + /**jsdoc + * @function LODManager.getLODFeedbackText + * @returns {string} + */ + Q_INVOKABLE QString getLODFeedbackText(); + + /**jsdoc + * @function LODManager.setOctreeSizeScale + * @param {number} sizeScale + */ + Q_INVOKABLE void setOctreeSizeScale(float sizeScale); + + /**jsdoc + * @function LODManager.getOctreeSizeScale + * @returns {number} + */ + Q_INVOKABLE float getOctreeSizeScale() const { return _octreeSizeScale; } + + /**jsdoc + * @function LODManager.setBoundaryLevelAdjust + * @param {number} boundaryLevelAdjust + */ + Q_INVOKABLE void setBoundaryLevelAdjust(int boundaryLevelAdjust); + + /**jsdoc + * @function LODManager.getBoundaryLevelAdjust + * @returns {number} + */ + Q_INVOKABLE int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } + + /**jsdoc + * @function LODManager.getLODDecreaseFPS + * @returns {number} + */ + Q_INVOKABLE float getLODDecreaseFPS() const; + + /**jsdoc + * @function LODManager.getLODIncreaseFPS + * @returns {number} + */ + Q_INVOKABLE float getLODIncreaseFPS() const; + float getPresentTime() const { return _presentTime; } float getEngineRunTime() const { return _engineRunTime; } float getGPUTime() const { return _gpuTime; } @@ -88,7 +172,17 @@ public: float getLODLevel() const; signals: + + /**jsdoc + * @function LODManager.LODIncreased + * @returns {Signal} + */ void LODIncreased(); + + /**jsdoc + * @function LODManager.LODDecreased + * @returns {Signal} + */ void LODDecreased(); private: diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4384635147..50ff65ad1a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -810,6 +810,9 @@ Menu::Menu() { scriptEngines->loadScript(defaultScriptsLoc.toString()); }); + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::VerboseLogging, 0, false, + qApp, SLOT(updateVerboseLogging())); + #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); QAction* backAction = addActionToQMenuAndActionHash(navigateMenu, MenuOption::Back, 0, addressManager.data(), SLOT(goBack())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index bba70a6a89..c8c8ee42df 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -142,6 +142,7 @@ namespace MenuOption { const QString Pair = "Pair"; const QString PhysicsShowHulls = "Draw Collision Shapes"; const QString PhysicsShowOwned = "Highlight Simulation Ownership"; + const QString VerboseLogging = "Verbose Logging"; const QString PipelineWarnings = "Log Render Pipeline Warnings"; const QString Preferences = "General..."; const QString Quit = "Quit"; diff --git a/interface/src/SpeechRecognizer.h b/interface/src/SpeechRecognizer.h index 9d18f14e3a..d5f9031cfc 100644 --- a/interface/src/SpeechRecognizer.h +++ b/interface/src/SpeechRecognizer.h @@ -22,6 +22,9 @@ #include +/**jsdoc + * @namespace SpeechRecognizer + */ class SpeechRecognizer : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -31,12 +34,39 @@ public: bool getEnabled() const { return _enabled; } public slots: + + /**jsdoc + * @function SpeechRecognizer.setEnabled + * @param {boolean} enabled + */ void setEnabled(bool enabled); + + /**jsdoc + * @function SpeechRecognizer.addCommand + * @param {string} command + */ void addCommand(const QString& command); + + /**jsdoc + * @function SpeechRecognizer.removeCommand + * @param {string} command + */ void removeCommand(const QString& command); signals: + + /**jsdoc + * @function SpeechRecognizer.commandRecognized + * @param {string} command + * @returns {Signal} + */ void commandRecognized(const QString& command); + + /**jsdoc + * @function SpeechRecognizer.enabledUpdated + * @param {boolean} enabled + * @returns {Signal} + */ void enabledUpdated(bool enabled); protected: diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index e99b8378e3..ff8bfda6dd 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -25,6 +25,17 @@ class AudioScope : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + /**jsdoc + * The AudioScope API helps control the Audio Scope features in Interface + * @namespace AudioScope + * @property {number} scopeInput Read-only. + * @property {number} scopeOutputLeft Read-only. + * @property {number} scopeOutputRight Read-only. + * @property {number} triggerInput Read-only. + * @property {number} triggerOutputLeft Read-only. + * @property {number} triggerOutputRight Read-only. + */ + Q_PROPERTY(QVector scopeInput READ getScopeInput) Q_PROPERTY(QVector scopeOutputLeft READ getScopeOutputLeft) Q_PROPERTY(QVector scopeOutputRight READ getScopeOutputRight) @@ -40,42 +51,164 @@ public: void reallocateScope(int frames); public slots: + + /**jsdoc + * @function AudioScope.toggle + */ void toggle() { setVisible(!_isEnabled); } + + /**jsdoc + * @function AudioScope.setVisible + * @param {boolean} visible + */ void setVisible(bool visible); + + /**jsdoc + * @function AudioScope.getVisible + * @returns {boolean} + */ bool getVisible() const { return _isEnabled; } + /**jsdoc + * @function AudioScope.togglePause + */ void togglePause() { setPause(!_isPaused); } + + /**jsdoc + * @function AudioScope.setPause + * @param {boolean} paused + */ void setPause(bool paused) { _isPaused = paused; emit pauseChanged(); } + + /**jsdoc + * @function AudioScope.getPause + * @returns {boolean} + */ bool getPause() { return _isPaused; } + /**jsdoc + * @function AudioScope.toggleTrigger + */ void toggleTrigger() { _autoTrigger = !_autoTrigger; } + + /**jsdoc + * @function AudioScope.getAutoTrigger + * @returns {boolean} + */ bool getAutoTrigger() { return _autoTrigger; } + + /**jsdoc + * @function AudioScope.setAutoTrigger + * @param {boolean} autoTrigger + */ void setAutoTrigger(bool autoTrigger) { _isTriggered = false; _autoTrigger = autoTrigger; } + /**jsdoc + * @function AudioScope.setTriggerValues + * @param {number} x + * @param {number} y + */ void setTriggerValues(int x, int y) { _triggerValues.x = x; _triggerValues.y = y; } + + /**jsdoc + * @function AudioScope.setTriggered + * @param {boolean} triggered + */ void setTriggered(bool triggered) { _isTriggered = triggered; } + + /**jsdoc + * @function AudioScope.getTriggered + * @returns {boolean} + */ bool getTriggered() { return _isTriggered; } + /**jsdoc + * @function AudioScope.getFramesPerSecond + * @returns {number} + */ float getFramesPerSecond(); + + /**jsdoc + * @function AudioScope.getFramesPerScope + * @returns {number} + */ int getFramesPerScope() { return _framesPerScope; } + /**jsdoc + * @function AudioScope.selectAudioScopeFiveFrames + */ void selectAudioScopeFiveFrames(); + + /**jsdoc + * @function AudioScope.selectAudioScopeTwentyFrames + */ void selectAudioScopeTwentyFrames(); + + /**jsdoc + * @function AudioScope.selectAudioScopeFiftyFrames + */ void selectAudioScopeFiftyFrames(); + /**jsdoc + * @function AudioScope.getScopeInput + * @returns {number[]} + */ QVector getScopeInput() { return _scopeInputData; }; + + /**jsdoc + * @function AudioScope.getScopeOutputLeft + * @returns {number[]} + */ QVector getScopeOutputLeft() { return _scopeOutputLeftData; }; + + /**jsdoc + * @function AudioScope.getScopeOutputRight + * @returns {number[]} + */ QVector getScopeOutputRight() { return _scopeOutputRightData; }; + /**jsdoc + * @function AudioScope.getTriggerInput + * @returns {number[]} + */ QVector getTriggerInput() { return _triggerInputData; }; + + /**jsdoc + * @function AudioScope.getTriggerOutputLeft + * @returns {number[]} + */ QVector getTriggerOutputLeft() { return _triggerOutputLeftData; }; + + /**jsdoc + * @function AudioScope.getTriggerOutputRight + * @returns {number[]} + */ QVector getTriggerOutputRight() { return _triggerOutputRightData; }; - void setLocalEcho(bool serverEcho); + /**jsdoc + * @function AudioScope.setLocalEcho + * @parm {boolean} localEcho + */ + void setLocalEcho(bool localEcho); + + /**jsdoc + * @function AudioScope.setServerEcho + * @parm {boolean} serverEcho + */ void setServerEcho(bool serverEcho); signals: + + /**jsdoc + * @function AudioScope.pauseChanged + * @returns {Signal} + */ void pauseChanged(); + + /**jsdoc + * @function AudioScope.triggered + * @returns {Signal} + */ void triggered(); protected: diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index fc93bbbfe3..d2655914d2 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -27,12 +27,17 @@ #include "AvatarMotionState.h" #include "MyAvatar.h" +/**jsdoc + * The AvatarManager API has properties and methods which manage Avatars within the same domain. + * @namespace AvatarManager + */ class AvatarManager : public AvatarHashMap { Q_OBJECT SINGLETON_DEPENDENCY public: + /// Registers the script types associated with the avatar manager. static void registerMetaTypes(QScriptEngine* engine); @@ -43,6 +48,11 @@ public: std::shared_ptr getMyAvatar() { return _myAvatar; } glm::vec3 getMyAvatarPosition() const { return _myAvatar->getWorldPosition(); } + /**jsdoc + * @function AvatarManager.getAvatar + * @param {Uuid} avatarID + * @returns {AvatarData} + */ // Null/Default-constructed QUuids will return MyAvatar Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) override { return new ScriptAvatar(getAvatarBySessionID(avatarID)); } @@ -66,24 +76,76 @@ public: void handleChangedMotionStates(const VectorOfMotionStates& motionStates); void handleCollisionEvents(const CollisionEvents& collisionEvents); + /**jsdoc + * @function AvatarManager.getAvatarDataRate + * @param {Uuid} sessionID + * @param {string} [rateName=""] + * @returns {number} + */ Q_INVOKABLE float getAvatarDataRate(const QUuid& sessionID, const QString& rateName = QString("")) const; + + /**jsdoc + * @function AvatarManager.getAvatarUpdateRate + * @param {Uuid} sessionID + * @param {string} [rateName=""] + * @returns {number} + */ + Q_INVOKABLE float getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName = QString("")) const; + + /**jsdoc + * @function AvatarManager.getAvatarSimulationRate + * @param {Uuid} sessionID + * @param {string} [rateName=""] + * @returns {number} + */ + Q_INVOKABLE float getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName = QString("")) const; + /**jsdoc + * @function AvatarManager.findRayIntersection + * @param {PickRay} ray + * @param {Uuid[]} [avatarsToInclude=[]] + * @param {Uuid[]} [avatarsToDiscard=[]] + * @returns {RayToAvatarIntersectionResult} + */ Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude = QScriptValue(), const QScriptValue& avatarIdsToDiscard = QScriptValue()); + /**jsdoc + * @function AvatarManager.findRayIntersectionVector + * @param {PickRay} ray + * @param {Uuid[]} avatarsToInclude + * @param {Uuid[]} avatarsToDiscard + * @returns {RayToAvatarIntersectionResult} + */ Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionVector(const PickRay& ray, const QVector& avatarsToInclude, const QVector& avatarsToDiscard); + /**jsdoc + * @function AvatarManager.getAvatarSortCoefficient + * @param {string} name + * @returns {number} + */ // TODO: remove this HACK once we settle on optimal default sort coefficients Q_INVOKABLE float getAvatarSortCoefficient(const QString& name); + + /**jsdoc + * @function AvatarManager.setAvatarSortCoefficient + * @param {string} name + * @param {number} value + */ Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value); float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); } public slots: + + /**jsdoc + * @function AvatarManager.updateAvatarRenderStatus + * @param {boolean} shouldRenderAvatars + */ void updateAvatarRenderStatus(bool shouldRenderAvatars); private: diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6f82c7dfb9..74f7a3c89f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -55,68 +55,119 @@ class MyAvatar : public Avatar { Q_OBJECT /**jsdoc - * Your avatar is your in-world representation of you. The MyAvatar API is used to manipulate the avatar. - * For example, using the MyAvatar API you can customize the avatar's appearance, run custom avatar animations, + * Your avatar is your in-world representation of you. The MyAvatar API is used to manipulate the avatar. + * For example, you can customize the avatar's appearance, run custom avatar animations, * change the avatar's position within the domain, or manage the avatar's collisions with other objects. - * NOTE: MyAvatar extends Avatar and AvatarData, see those namespace for more properties/methods. * * @namespace MyAvatar - * @augments Avatar - * @property qmlPosition {Vec3} Used as a stopgap for position access by QML, as glm::vec3 is unavailable outside of scripts - * @property shouldRenderLocally {bool} Set it to true if you would like to see MyAvatar in your local interface, - * and false if you would not like to see MyAvatar in your local interface. - * @property motorVelocity {Vec3} Can be used to move the avatar with this velocity. - * @property motorTimescale {float} Specifies how quickly the avatar should accelerate to meet the motorVelocity, - * smaller values will result in higher acceleration. - * @property motorReferenceFrame {string} Reference frame of the motorVelocity, must be one of the following: "avatar", "camera", "world" - * @property motorMode {string} Type of scripted motor behavior, "simple" = use motorTimescale property (default mode) and "dynamic" = use action motor's timescales - * @property collisionSoundURL {string} Specifies the sound to play when the avatar experiences a collision. - * You can provide a mono or stereo 16-bit WAV file running at either 24 Khz or 48 Khz. - * The latter is downsampled by the audio mixer, so all audio effectively plays back at a 24 Khz sample rate. - * 48 Khz RAW files are also supported. - * @property audioListenerMode {number} When hearing spatialized audio this determines where the listener placed. - * Should be one of the following values: - * MyAvatar.audioListenerModeHead - the listener located at the avatar's head. - * MyAvatar.audioListenerModeCamera - the listener is relative to the camera. - * MyAvatar.audioListenerModeCustom - the listener is at a custom location specified by the MyAvatar.customListenPosition - * and MyAvatar.customListenOrientation properties. - * @property customListenPosition {Vec3} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the position - * of audio spatialization listener. - * @property customListenOrientation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation - * of the audio spatialization listener. - * @property audioListenerModeHead {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener - * around the avatar's head. - * @property audioListenerModeCamera {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener - * around the camera. - * @property audioListenerModeCustom {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener - * around the value specified by MyAvatar.customListenPosition and MyAvatar.customListenOrientation. - * @property leftHandPosition {Vec3} READ-ONLY. The desired position of the left wrist in avatar space, determined by the hand controllers. - * Note: only valid if hand controllers are in use. - * @property rightHandPosition {Vec3} READ-ONLY. The desired position of the right wrist in avatar space, determined by the hand controllers. - * Note: only valid if hand controllers are in use. - * @property leftHandTipPosition {Vec3} READ-ONLY. A position 30 cm offset from MyAvatar.leftHandPosition - * @property rightHandTipPosition {Vec3} READ-ONLY. A position 30 cm offset from MyAvatar.rightHandPosition - * @property leftHandPose {Pose} READ-ONLY. Returns full pose (translation, orientation, velocity & angularVelocity) of the desired - * wrist position, determined by the hand controllers. - * @property rightHandPose {Pose} READ-ONLY. Returns full pose (translation, orientation, velocity & angularVelocity) of the desired - * wrist position, determined by the hand controllers. - * @property leftHandTipPose {Pose} READ-ONLY. Returns a pose offset 30 cm from MyAvatar.leftHandPose - * @property rightHandTipPose {Pose} READ-ONLY. Returns a pose offset 30 cm from MyAvatar.rightHandPose - * @property hmdLeanRecenterEnabled {bool} This can be used disable the hmd lean recenter behavior. This behavior is what causes your avatar - * to follow your HMD as you walk around the room, in room scale VR. Disabling this is useful if you desire to pin the avatar to a fixed location. - * @property collisionsEnabled {bool} This can be used to disable collisions between the avatar and the world. - * @property useAdvancedMovementControls {bool} Stores the user preference only, does not change user mappings, this is done in the defaultScript - * "scripts/system/controllers/toggleAdvancedMovementForHandControllers.js". - * @property userHeight {number} The height of the user in sensor space. (meters). - * @property userEyeHeight {number} Estimated height of the users eyes in sensor space. (meters) - * @property SELF_ID {string} READ-ONLY. UUID representing "my avatar". Only use for local-only entities and overlays in situations where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). - * Note: Likely to be deprecated. - * @property hmdRollControlEnabled {bool} When enabled the roll angle of your HMD will turn your avatar while flying. - * @property hmdRollControlDeadZone {number} If hmdRollControlEnabled is true, this value can be used to tune what roll angle is required to begin turning. - * This angle is specified in degrees. - * @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of your avatar when rolling your HMD in degrees per second. - */ + * + * @hifi-interface + * @hifi-client-entity + * + * @property {Vec3} qmlPosition - A synonym for position for use by QML. + * @property {boolean} shouldRenderLocally=true - If true then your avatar is rendered for you in Interface, + * otherwise it is not rendered for you (but it is still rendered for other users). + * @property {Vec3} motorVelocity=Vec3.ZERO - The target velocity of your avatar to be achieved by a scripted motor. + * @property {number} motorTimescale=1000000 - The timescale for the scripted motor to achieve the target + * motorVelocity avatar velocity. Smaller values result in higher acceleration. + * @property {string} motorReferenceFrame="camera" - Reference frame of the motorVelocity. Must be one of the + * following: "camera", "avatar", and "world". + * @property {string} motorMode="simple" - The Type of scripted motor behavior: "simple" to use the + * motorTimescale time scale; "dynamic" to use character controller timescales. + * @property {string} collisionSoundURL="Body_Hits_Impact.wav" - The sound that's played when the avatar experiences a + * collision. It can be a mono or stereo 16-bit WAV file running at either 24kHz or 48kHz. The latter is down-sampled + * by the audio mixer, so all audio effectively plays back at a 24khz. 48kHz RAW files are also supported. + * @property {number} audioListenerMode=0 - Specifies the listening position when hearing spatialized audio. Must be one + * of the following property values:
+ * audioListenerModeHead
+ * audioListenerModeCamera
+ * audioListenerModeCustom + * @property {number} audioListenerModeHead=0 - The audio listening position is at the avatar's head. Read-only. + * @property {number} audioListenerModeCamera=1 - The audio listening position is at the camera. Read-only. + * @property {number} audioListenerModeCustom=2 - The audio listening position is at a the position specified by set by the + * customListenPosition and customListenOrientation property values. Read-only. + * @property {Vec3} customListenPosition=Vec3.ZERO - The listening position used when the audioListenerMode + * property value is audioListenerModeCustom. + * @property {Quat} customListenOrientation=Quat.IDENTITY - The listening orientation used when the + * audioListenerMode property value is audioListenerModeCustom. + * @property {Vec3} leftHandPosition - The position of the left hand in avatar coordinates if it's being positioned by + * controllers, otherwise {@link Vec3(0)|Vec3.ZERO}. Read-only. + * @property {Vec3} rightHandPosition - The position of the right hand in avatar coordinates if it's being positioned by + * controllers, otherwise {@link Vec3(0)|Vec3.ZERO}. Read-only. + * @property {Vec3} leftHandTipPosition - The position 30cm offset from the left hand in avatar coordinates if it's being + * positioned by controllers, otherwise {@link Vec3(0)|Vec3.ZERO}. Read-only. + * @property {Vec3} rightHandTipPosition - The position 30cm offset from the right hand in avatar coordinates if it's being + * positioned by controllers, otherwise {@link Vec3(0)|Vec3.ZERO}. Read-only. + * @property {Pose} leftHandPose - The pose of the left hand as determined by the hand controllers. Read-only. + * @property {Pose} rightHandPose - The pose right hand position as determined by the hand controllers. Read-only. + * @property {Pose} leftHandTipPose - The pose of the left hand as determined by the hand controllers, with the position + * by 30cm. Read-only. + * @property {Pose} rightHandTipPose - The pose of the right hand as determined by the hand controllers, with the position + * by 30cm. Read-only. + * @property {boolean} hmdLeanRecenterEnabled=true - If true then the avatar is re-centered to be under the + * head's position. In room-scale VR, this behavior is what causes your avatar to follow your HMD as you walk around + * the room. Setting the value false is useful if you want to pin the avatar to a fixed position. + * @property {boolean} collisionsEnabled - Set to true to enable collisions for the avatar, false + * to disable collisions. May return true even though the value was set false because the + * zone may disallow collisionless avatars. + * @property {boolean} characterControllerEnabled - Synonym of collisionsEnabled. + * Deprecated: Use collisionsEnabled instead. + * @property {boolean} useAdvancedMovementControls - Returns the value of the Interface setting, Settings > Advanced + * Movement for Hand Controller. Note: Setting the value has no effect unless Interface is restarted. + * @property {number} yawSpeed=75 + * @property {number} pitchSpeed=50 + * @property {boolean} hmdRollControlEnabled=true - If true, the roll angle of your HMD turns your avatar + * while flying. + * @property {number} hmdRollControlDeadZone=8 - The amount of HMD roll, in degrees, required before your avatar turns if + * hmdRollControlEnabled is enabled. + * @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of + * your avatar when rolling your HMD in degrees per second. + * @property {number} userHeight=1.75 - The height of the user in sensor space. + * @property {number} userEyeHeight=1.65 - The estimated height of the user's eyes in sensor space. Read-only. + * @property {Uuid} SELF_ID - UUID representing "my avatar". Only use for local-only entities and overlays in situations + * where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). Note: Likely to be deprecated. + * Read-only. + * @property {number} walkSpeed + * + * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the + * registration point of the 3D model. + * + * @property {Vec3} position + * @property {number} scale + * @property {number} density Read-only. + * @property {Vec3} handPosition + * @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of MyAvatar. Yaw + * is sometimes called "heading". + * @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of MyAvatar. Pitch is + * sometimes called "elevation". + * @property {number} bodyRoll - The rotation about an axis running from the chest to the back of the avatar. Roll is + * sometimes called "bank". + * @property {Quat} orientation + * @property {Quat} headOrientation - The orientation of the avatar's head. + * @property {number} headPitch - The rotation about an axis running from ear to ear of the avatar's head. Pitch is + * sometimes called "elevation". + * @property {number} headYaw - The rotation left or right about an axis running from the base to the crown of the avatar's + * head. Yaw is sometimes called "heading". + * @property {number} headRoll - The rotation about an axis running from the nose to the back of the avatar's head. Roll is + * sometimes called "bank". + * @property {Vec3} velocity + * @property {Vec3} angularVelocity + * @property {number} audioLoudness + * @property {number} audioAverageLoudness + * @property {string} displayName + * @property {string} sessionDisplayName - Sanitized, defaulted version displayName that is defined by the AvatarMixer + * rather than by Interface clients. The result is unique among all avatars present at the time. + * @property {boolean} lookAtSnappingEnabled + * @property {string} skeletonModelURL + * @property {AttachmentData[]} attachmentData + * @property {string[]} jointNames - The list of joints in the current avatar model. Read-only. + * @property {Uuid} sessionUUID Read-only. + * @property {Mat4} sensorToWorldMatrix Read-only. + * @property {Mat4} controllerLeftHandMatrix Read-only. + * @property {Mat4} controllerRightHandMatrix Read-only. + * @property {number} sensorToWorldScale Read-only. + */ // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition) QVector3D getQmlPosition() { auto p = getWorldPosition(); return QVector3D(p.x, p.y, p.z); } @@ -200,6 +251,9 @@ public: void reset(bool andRecenter = false, bool andReload = true, bool andHead = true); + /**jsdoc + * @function MyAvatar.resetSensorsAndBody + */ Q_INVOKABLE void resetSensorsAndBody(); /**jsdoc @@ -224,7 +278,16 @@ public: const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; } const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; } + /**jsdoc + * @function MyAvatar.setOrientationVar + * @param {object} newOrientationVar + */ Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar); + + /**jsdoc + * @function MyAvatar.getOrientationVar + * @returns {object} + */ Q_INVOKABLE QVariant getOrientationVar() const; // A method intended to be overriden by MyAvatar for polling orientation for network transmission. @@ -246,12 +309,14 @@ public: void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); } /**jsdoc - * The default position in world coordinates of the point directly between the avatar's eyes + * Get the position in world coordinates of the point directly between your avatar's eyes assuming your avatar was in its + * default pose. This is a reference position; it does not change as your avatar's head moves relative to the avatar + * position. * @function MyAvatar.getDefaultEyePosition - * @example This example gets the default eye position and prints it to the debug log. + * @returns {Vec3} Default position between your avatar's eyes in world coordinates. + * @example Report your avatar's default eye position. * var defaultEyePosition = MyAvatar.getDefaultEyePosition(); - * print (JSON.stringify(defaultEyePosition)); - * @returns {Vec3} Position between the avatar's eyes. + * print(JSON.stringify(defaultEyePosition)); */ Q_INVOKABLE glm::vec3 getDefaultEyePosition() const; @@ -261,8 +326,19 @@ public: * The avatar animation system includes a set of default animations along with rules for how those animations are blended * together with procedural data (such as look at vectors, hand sensors etc.). overrideAnimation() is used to completely * override all motion from the default animation system (including inverse kinematics for hand and head controllers) and - * play a specified animation. To end this animation and restore the default animations, use MyAvatar.restoreAnimation. + * play a set of specified animations. To end these animations and restore the default animations, use + * {@link MyAvatar.restoreAnimation}.
+ *

Note: When using pre-built animation data, it's critical that the joint orientation of the source animation and target + * rig are equivalent, since the animation data applies absolute values onto the joints. If the orientations are different, + * the avatar will move in unpredictable ways. For more information about avatar joint orientation standards, see + * Avatar Standards.

* @function MyAvatar.overrideAnimation + * @param url {string} The URL to the animation file. Animation files need to be .FBX format, but only need to contain the + * avatar skeleton and animation data. + * @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. + * @param loop {boolean} Set to true if the animation should loop. + * @param firstFrame {number} The frame the animation should start at. + * @param lastFrame {number} The frame the animation should end at. * @example Play a clapping animation on your avatar for three seconds. * // Clap your hands for 3 seconds then restore animation back to the avatar. * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; @@ -270,11 +346,6 @@ public: * Script.setTimeout(function () { * MyAvatar.restoreAnimation(); * }, 3000); - * @param url {string} The URL to the animation file. Animation files need to be .FBX format, but only need to contain the avatar skeleton and animation data. - * @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. - * @param loop {bool} Set to true if the animation should loop. - * @param firstFrame {number} The frame the animation should start at. - * @param lastFrame {number} The frame the animation should end at. */ Q_INVOKABLE void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); @@ -300,13 +371,13 @@ public: * Animation roles map to easily understandable actions that the avatar can perform, such as "idleStand", "idleTalk", or "walkFwd." * getAnimationRoles() is used get the list of animation roles defined in the avatar-animation.json. * @function MyAvatar.getAnimationRoles - * @example This example prints the list of animation roles defined in the avatar's avatar-animation.json file to the debug log. + * @returns {string[]} Array of role strings. + * @example Print the list of animation roles defined in the avatar's avatar-animation.json file to the debug log. * var roles = MyAvatar.getAnimationRoles(); * print("Animation Roles:"); * for (var i = 0; i < roles.length; i++) { * print(roles[i]); * } - * @returns {string[]} Array of role strings */ Q_INVOKABLE QStringList getAnimationRoles(); @@ -315,25 +386,32 @@ public: * that the avatar can perform, such as "idleStand", "idleTalk", or "walkFwd". To get the full list of roles, use getAnimationRoles(). * For each role, the avatar-animation.json defines when the animation is used, the animation clip (.FBX) used, and how animations are blended * together with procedural data (such as look at vectors, hand sensors etc.). - * overrideRoleAnimation() is used to change the animation clip (.FBX) associated with a specified animation role. - * Note: Hand roles only affect the hand. Other 'main' roles, like 'idleStand', 'idleTalk', 'takeoffStand' are full body. + * overrideRoleAnimation() is used to change the animation clip (.FBX) associated with a specified animation role. To end + * the animations and restore the default animations, use {@link MyAvatar.restoreRoleAnimation}.
+ *

Note: Hand roles only affect the hand. Other 'main' roles, like 'idleStand', 'idleTalk', 'takeoffStand' are full body.

+ *

Note: When using pre-built animation data, it's critical that the joint orientation of the source animation and target + * rig are equivalent, since the animation data applies absolute values onto the joints. If the orientations are different, + * the avatar will move in unpredictable ways. For more information about avatar joint orientation standards, see + * Avatar Standards. * @function MyAvatar.overrideRoleAnimation + * @param role {string} The animation role to override + * @param url {string} The URL to the animation file. Animation files need to be .FBX format, but only need to contain the avatar skeleton and animation data. + * @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. + * @param loop {boolean} Set to true if the animation should loop + * @param firstFrame {number} The frame the animation should start at + * @param lastFrame {number} The frame the animation should end at * @example The default avatar-animation.json defines an "idleStand" animation role. This role specifies that when the avatar is not moving, * an animation clip of the avatar idling with hands hanging at its side will be used. It also specifies that when the avatar moves, the animation * will smoothly blend to the walking animation used by the "walkFwd" animation role. * In this example, the "idleStand" role animation clip has been replaced with a clapping animation clip. Now instead of standing with its arms * hanging at its sides when it is not moving, the avatar will stand and clap its hands. Note that just as it did before, as soon as the avatar * starts to move, the animation will smoothly blend into the walk animation used by the "walkFwd" animation role. - * // An animation of the avatar clapping its hands while standing + * // An animation of the avatar clapping its hands while standing. Restore default after 30s. * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; * MyAvatar.overrideRoleAnimation("idleStand", ANIM_URL, 30, true, 0, 53); - * // To restore the default animation, use MyAvatar.restoreRoleAnimation(). - * @param role {string} The animation role to override - * @param url {string} The URL to the animation file. Animation files need to be .FBX format, but only need to contain the avatar skeleton and animation data. - * @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. - * @param loop {bool} Set to true if the animation should loop - * @param firstFrame {number} The frame the animation should start at - * @param lastFrame {number} The frame the animation should end at + * Script.setTimeout(function () { + * MyAvatar.restoreRoleAnimation(); + * }, 30000); */ Q_INVOKABLE void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame); @@ -346,7 +424,7 @@ public: * restoreRoleAnimation() is used to restore a specified animation role's default animation clip. If you have not specified an override animation * for the specified role, this function will have no effect. * @function MyAvatar.restoreRoleAnimation - * @param role {string} The animation role clip to restore + * @param role {string} The animation role clip to restore. */ Q_INVOKABLE void restoreRoleAnimation(const QString& role); @@ -360,18 +438,58 @@ public: // a handler must not remove properties from animStateDictionaryIn, nor change property values that it does not intend to change. // It is not specified in what order multiple handlers are called. Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _skeletonModel->getRig().addAnimationStateHandler(handler, propertiesList); } + + /**jsdoc + * @function MyAvatar.removeAnimationStateHandler + * @param {number} handler + */ // Removes a handler previously added by addAnimationStateHandler. Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _skeletonModel->getRig().removeAnimationStateHandler(handler); } + + /**jsdoc + * @function MyAvatar.getSnapTurn + * @returns {boolean} + */ Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } + /**jsdoc + * @function MyAvatar.setSnapTurn + * @param {boolean} on + */ Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } + /**jsdoc + * @function MyAvatar.getClearOverlayWhenMoving + * @returns {boolean} + */ Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; } + /**jsdoc + * @function MyAvatar.setClearOverlayWhenMoving + * @returns {boolean} + */ Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; } + + /**jsdoc + * @function MyAvatar.setDominantHand + * @param {string} hand + */ Q_INVOKABLE void setDominantHand(const QString& hand); + /**jsdoc + * @function MyAvatar.getDominantHand + * @returns {string} + */ Q_INVOKABLE QString getDominantHand() const { return _dominantHand; } + + /**jsdoc + * @function MyAvatar.setHMDLeanRecenterEnabled + * @param {boolean} enabled + */ Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; } + /**jsdoc + * @function MyAvatar.getHMDLeanRecenterEnabled + * @returns {boolean} + */ Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; } bool useAdvancedMovementControls() const { return _useAdvancedMovementControls.get(); } @@ -397,70 +515,241 @@ public: void setDriveKey(DriveKeys key, float val); void setSprintMode(bool sprint); float getDriveKey(DriveKeys key) const; + + /**jsdoc + * @function MyAvatar.getRawDriveKey + * @param {DriveKeys} key + * @returns {number} + */ Q_INVOKABLE float getRawDriveKey(DriveKeys key) const; + void relayDriveKeysToCharacterController(); + /**jsdoc + * @function MyAvatar.disableDriveKey + * @param {DriveKeys} key + */ Q_INVOKABLE void disableDriveKey(DriveKeys key); + + /**jsdoc + * @function MyAvatar.enableDriveKey + * @param {DriveKeys} key + */ Q_INVOKABLE void enableDriveKey(DriveKeys key); + /**jsdoc + * @function MyAvatar.isDriveKeyDisabled + * @param {DriveKeys} key + * @returns {boolean} + */ Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const; - /**jsdoc - *The triggerVerticalRecenter function activates one time the recentering - *behaviour in the vertical direction. This call is only takes effect when the property - *MyAvatar.hmdLeanRecenterEnabled is set to false. - *@function MyAvatar.triggerVerticalRecenter - * - */ /**jsdoc - *The triggerHorizontalRecenter function activates one time the recentering behaviour - *in the horizontal direction. This call is only takes effect when the property - *MyAvatar.hmdLeanRecenterEnabled is set to false. - *@function MyAvatar.triggerHorizontalRecenter - */ - - /**jsdoc - *The triggerRotationRecenter function activates one time the recentering behaviour - *in the rotation of the root of the avatar. This call is only takes effect when the property - *MyAvatar.hmdLeanRecenterEnabled is set to false. - *@function MyAvatar.triggerRotationRecenter - */ - + * Recenter the avatar in the vertical direction, if {@link MyAvatar|MyAvatar.hmdLeanRecenterEnabled} is + * false. + * @function MyAvatar.triggerVerticalRecenter + */ Q_INVOKABLE void triggerVerticalRecenter(); + + /**jsdoc + * Recenter the avatar in the horizontal direction, if {@link MyAvatar|MyAvatar.hmdLeanRecenterEnabled} is + * false. + * @ function MyAvatar.triggerHorizontalRecenter + */ Q_INVOKABLE void triggerHorizontalRecenter(); + + /**jsdoc + * Recenter the avatar's rotation, if {@link MyAvatar|MyAvatar.hmdLeanRecenterEnabled} is false. + * @function MyAvatar.triggerRotationRecenter + */ Q_INVOKABLE void triggerRotationRecenter(); + eyeContactTarget getEyeContactTarget(); const MyHead* getMyHead() const; + + /**jsdoc + * Get the current position of the avatar's "Head" joint. + * @function MyAvatar.getHeadPosition + * @returns {Vec3} The current position of the avatar's "Head" joint. + * @example Report the current position of your avatar's head. + * print(JSON.stringify(MyAvatar.getHeadPosition())); + */ Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); } + + /**jsdoc + * @function MyAvatar.getHeadFinalYaw + * @returns {number} + */ Q_INVOKABLE float getHeadFinalYaw() const { return getHead()->getFinalYaw(); } + + /**jsdoc + * @function MyAvatar.getHeadFinalRoll + * @returns {number} + */ Q_INVOKABLE float getHeadFinalRoll() const { return getHead()->getFinalRoll(); } + + /**jsdoc + * @function MyAvatar.getHeadFinalPitch + * @returns {number} + */ Q_INVOKABLE float getHeadFinalPitch() const { return getHead()->getFinalPitch(); } + + /**jsdoc + * @function MyAvatar.getHeadDeltaPitch + * @returns {number} + */ Q_INVOKABLE float getHeadDeltaPitch() const { return getHead()->getDeltaPitch(); } + /**jsdoc + * Get the current position of the point directly between the avatar's eyes. + * @function MyAvatar.getEyePosition + * @returns {Vec3} The current position of the point directly between the avatar's eyes. + * @example Report your avatar's current eye position. + * var eyePosition = MyAvatar.getEyePosition(); + * print(JSON.stringify(eyePosition)); + */ Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); } + /**jsdoc + * @function MyAvatar.getTargetAvatarPosition + * @returns {Vec3} The position of the avatar you're currently looking at. + * @example Report the position of the avatar you're currently looking at. + * print(JSON.stringify(MyAvatar.getTargetAvatarPosition())); + */ Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; } + + /**jsdoc + * @function MyAvatar.getTargetAvatar + * @returns {AvatarData} + */ Q_INVOKABLE ScriptAvatarData* getTargetAvatar() const; + + /**jsdoc + * Get the position of the avatar's left hand as positioned by a hand controller (e.g., Oculus Touch or Vive).
+ *

Note: The Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints + * for hand animation.)

+ * @function MyAvatar.getLeftHandPosition + * @returns {Vec3} The position of the left hand in avatar coordinates if positioned by a hand controller, otherwise + * {@link Vec3(0)|Vec3.ZERO}. + * @example Report the position of your left hand relative to your avatar. + * print(JSON.stringify(MyAvatar.getLeftHandPosition())); + */ Q_INVOKABLE glm::vec3 getLeftHandPosition() const; + + /**jsdoc + * Get the position of the avatar's right hand as positioned by a hand controller (e.g., Oculus Touch or Vive).
+ *

Note: The Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints + * for hand animation.)

+ * @function MyAvatar.getRightHandPosition + * @returns {Vec3} The position of the right hand in avatar coordinates if positioned by a hand controller, otherwise + * {@link Vec3(0)|Vec3.ZERO}. + * @example Report the position of your right hand relative to your avatar. + * print(JSON.stringify(MyAvatar.getLeftHandPosition())); + */ Q_INVOKABLE glm::vec3 getRightHandPosition() const; + + /**jsdoc + * @function MyAvatar.getLeftHandTipPosition + * @returns {Vec3} + */ Q_INVOKABLE glm::vec3 getLeftHandTipPosition() const; + + /**jsdoc + * @function MyAvatar.getRightHandTipPosition + * @returns {Vec3} + */ Q_INVOKABLE glm::vec3 getRightHandTipPosition() const; + + /**jsdoc + * Get the pose (position, rotation, velocity, and angular velocity) of the avatar's left hand as positioned by a + * hand controller (e.g., Oculus Touch or Vive).
+ *

Note: The Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints + * for hand animation.) If you are using the Leap Motion, the return value's valid property will be + * false and any pose values returned will not be meaningful.

+ * @function MyAvatar.getLeftHandPose + * @returns {Pose} + * @example Report the pose of your avatar's left hand. + * print(JSON.stringify(MyAvatar.getLeftHandPose())); + */ Q_INVOKABLE controller::Pose getLeftHandPose() const; + + /**jsdoc + * Get the pose (position, rotation, velocity, and angular velocity) of the avatar's left hand as positioned by a + * hand controller (e.g., Oculus Touch or Vive).
+ *

Note: The Leap Motion isn't part of the hand controller input system. (Instead, it manipulates the avatar's joints + * for hand animation.) If you are using the Leap Motion, the return value's valid property will be + * false and any pose values returned will not be meaningful.

+ * @function MyAvatar.getRightHandPose + * @returns {Pose} + * @example Report the pose of your avatar's right hand. + * print(JSON.stringify(MyAvatar.getRightHandPose())); + */ Q_INVOKABLE controller::Pose getRightHandPose() const; + + /**jsdoc + * @function MyAvatar.getLeftHandTipPose + * @returns {Pose} + */ Q_INVOKABLE controller::Pose getLeftHandTipPose() const; + + /**jsdoc + * @function MyAvatar.getRightHandTipPose + * @returns {Pose} + */ Q_INVOKABLE controller::Pose getRightHandTipPose() const; // world-space to avatar-space rigconversion functions + /**jsdoc + * @function MyAvatar.worldToJointPoint + * @param {Vec3} position + * @param {number} [jointIndex=-1] + * @returns {Vec3} + */ Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const; + + /**jsdoc + * @function MyAvatar.worldToJointDirection + * @param {Vec3} direction + * @param {number} [jointIndex=-1] + * @returns {Vec3} + */ Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const; + + /**jsdoc + * @function MyAvatar.worldToJointRotation + * @param {Quat} rotation + * @param {number} [jointIndex=-1] + * @returns {Quat} + */ Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const; + + /**jsdoc + * @function MyAvatar.jointToWorldPoint + * @param {vec3} position + * @param {number} [jointIndex=-1] + * @returns {Vec3} + */ Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const; + + /**jsdoc + * @function MyAvatar.jointToWorldDirection + * @param {Vec3} direction + * @param {number} [jointIndex=-1] + * @returns {Vec3} + */ Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const; + + /**jsdoc + * @function MyAvatar.jointToWorldRotation + * @param {Quat} rotation + * @param {number} [jointIndex=-1] + * @returns {Quat} + */ Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } @@ -479,15 +768,55 @@ public: virtual void clearJointData(const QString& name) override; virtual void clearJointsData() override; + /**jsdoc + * @function MyAvatar.pinJoint + * @param {number} index + * @param {Vec3} position + * @param {Quat} orientation + * @returns {boolean} + */ Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation); + bool isJointPinned(int index); + + /**jsdoc + * @function MyAvatar.clearPinOnJoint + * @param {number} index + * @returns {boolean} + */ Q_INVOKABLE bool clearPinOnJoint(int index); + /**jsdoc + * @function MyAvatar.getIKErrorOnLastSolve + * @returns {number} + */ Q_INVOKABLE float getIKErrorOnLastSolve() const; + /**jsdoc + * @function MyAvatar.useFullAvatarURL + * @param {string} fullAvatarURL + * @param {string} [modelName=""] + */ Q_INVOKABLE void useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelName = QString()); + + /**jsdoc + * Get the complete URL for the current avatar. + * @function MyAvatar.getFullAvatarURLFromPreferences + * @returns {string} The full avatar model name. + * @example Report the URL for the current avatar. + * print(MyAvatar.getFullAvatarURLFromPreferences()); + */ Q_INVOKABLE QUrl getFullAvatarURLFromPreferences() const { return _fullAvatarURLFromPreferences; } + + /**jsdoc + * Get the full avatar model name for the current avatar. + * @function MyAvatar.getFullAvatarModelName + * @returns {string} The full avatar model name. + * @example Report the current full avatar model name. + * print(MyAvatar.getFullAvatarModelName()); + */ Q_INVOKABLE QString getFullAvatarModelName() const { return _fullAvatarModelName; } + void resetFullAvatarURL(); virtual void setAttachmentData(const QVector& attachmentData) override; @@ -544,17 +873,68 @@ public: QVariantList getAvatarEntitiesVariant(); void removeAvatarEntities(); + /**jsdoc + * @function MyAvatar.isFlying + * @returns {boolean} + */ Q_INVOKABLE bool isFlying(); + + /**jsdoc + * @function MyAvatar.isInAir + * @returns {boolean} + */ Q_INVOKABLE bool isInAir(); + + /**jsdoc + * @function MyAvatar.setFlyingEnabled + * @param {boolean} enabled + */ Q_INVOKABLE void setFlyingEnabled(bool enabled); + + /**jsdoc + * @function MyAvatar.getFlyingEnabled + * @returns {boolean} + */ Q_INVOKABLE bool getFlyingEnabled(); + + /**jsdoc + * @function MyAvatar.getAvatarScale + * @returns {number} + */ Q_INVOKABLE float getAvatarScale(); + + /**jsdoc + * @function MyAvatar.setAvatarScale + * @param {number} scale + */ Q_INVOKABLE void setAvatarScale(float scale); + + /**jsdoc + * @function MyAvatar.setCollisionsEnabled + * @param {boolean} enabled + */ Q_INVOKABLE void setCollisionsEnabled(bool enabled); + + /**jsdoc + * @function MyAvatar.getCollisionsEnabled + * @returns {boolean} + */ Q_INVOKABLE bool getCollisionsEnabled(); + + /**jsdoc + * @function MyAvatar.setCharacterControllerEnabled + * @param {boolean} enabled + * @deprecated + */ Q_INVOKABLE void setCharacterControllerEnabled(bool enabled); // deprecated + + /**jsdoc + * @function MyAvatar.getCharacterControllerEnabled + * @returns {boolean} + * @deprecated + */ Q_INVOKABLE bool getCharacterControllerEnabled(); // deprecated virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; @@ -580,7 +960,18 @@ public: // results are in HMD frame glm::mat4 deriveBodyFromHMDSensor() const; + /**jsdoc + * @function MyAvatar.isUp + * @param {Vec3} direction + * @returns {boolean} + */ Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up. + + /**jsdoc + * @function MyAvatar.isDown + * @param {Vec3} direction + * @returns {boolean} + */ Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; }; void setUserHeight(float value); @@ -595,69 +986,337 @@ public: float getWalkSpeed() const; public slots: + + /**jsdoc + * Increase the avatar's scale by five percent, up to a minimum scale of 1000. + * @function MyAvatar.increaseSize + * @example Reset your avatar's size to default then grow it 5 times. + * MyAvatar.resetSize(); + * + * for (var i = 0; i < 5; i++){ + * print ("Growing by 5 percent"); + * MyAvatar.increaseSize(); + * } + */ void increaseSize(); + + /**jsdoc + * Decrease the avatar's scale by five percent, down to a minimum scale of 0.25. + * @function MyAvatar.decreaseSize + * @example Reset your avatar's size to default then shrink it 5 times. + * MyAvatar.resetSize(); + * + * for (var i = 0; i < 5; i++){ + * print ("Shrinking by 5 percent"); + * MyAvatar.decreaseSize(); + * } + */ void decreaseSize(); + + /**jsdoc + * Reset the avatar's scale back to the default scale of 1.0. + * @function MyAvatar.resetSize + */ void resetSize(); + + /**jsdoc + * @function MyAvatar.animGraphLoaded + */ void animGraphLoaded(); + /**jsdoc + * @function MyAvatar.setGravity + * @param {number} gravity + */ void setGravity(float gravity); + + /**jsdoc + * @function MyAvatar.getGravity + * @returns {number} + */ float getGravity(); + /**jsdoc + * Move the avatar to a new position and/or orientation in the domain. + * @function MyAvatar.goToLocation + * @param {Vec3} position - The new position for the avatar, in world coordinates. + * @param {boolean} [hasOrientation=false] - Set to true to set the orientation of the avatar. + * @param {Quat} [orientation=Quat.IDENTITY] - The new orientation for the avatar. + * @param {boolean} [shouldFaceLocation=false] - Set to true to position the avatar a short distance away from + * the new position and orientate the avatar to face the position. + */ void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false); + /**jsdoc + * @function MyAvatar.goToLocation + * @param {object} properties + */ void goToLocation(const QVariant& properties); + + /**jsdoc + * @function MyAvatar.goToLocationAndEnableCollisions + * @param {Vec3} position + */ void goToLocationAndEnableCollisions(const glm::vec3& newPosition); + + /**jsdoc + * @function MyAvatar.safeLanding + * @param {Vec3} position + * @returns {boolean} + */ bool safeLanding(const glm::vec3& position); + + /**jsdoc + * @function MyAvatar.restrictScaleFromDomainSettings + * @param {objecct} domainSettingsObject + */ void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject); + + /**jsdoc + * @function MyAvatar.clearScaleRestriction + */ void clearScaleRestriction(); + + /**jsdoc + * @function MyAvatar.addThrust + * @param {Vec3} thrust + */ // Set/Get update the thrust that will move the avatar around void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; + + /**jsdoc + * @function MyAvatar.getThrust + * @returns {vec3} + */ glm::vec3 getThrust() { return _thrust; }; + + /**jsdoc + * @function MyAvatar.setThrust + * @param {Vec3} thrust + */ void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } + + /**jsdoc + * @function MyAvatar.updateMotionBehaviorFromMenu + */ Q_INVOKABLE void updateMotionBehaviorFromMenu(); + + /**jsdoc + * @function MyAvatar.setEnableDebugDrawDefaultPose + * @param {boolean} enabled + */ void setEnableDebugDrawDefaultPose(bool isEnabled); + /**jsdoc + * @function MyAvatar.setEnableDebugDrawAnimPose + * @param {boolean} enabled + */ void setEnableDebugDrawAnimPose(bool isEnabled); + /**jsdoc + * @function MyAvatar.setEnableDebugDrawPosition + * @param {boolean} enabled + */ void setEnableDebugDrawPosition(bool isEnabled); + /**jsdoc + * @function MyAvatar.setEnableDebugDrawHandControllers + * @param {boolean} enabled + */ void setEnableDebugDrawHandControllers(bool isEnabled); + /**jsdoc + * @function MyAvatar.setEnableDebugDrawSensorToWorldMatrix + * @param {boolean} enabled + */ void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled); + /**jsdoc + * @function MyAvatar.setEnableDebugDrawIKTargets + * @param {boolean} enabled + */ void setEnableDebugDrawIKTargets(bool isEnabled); + /**jsdoc + * @function MyAvatar.setEnableDebugDrawIKConstraints + * @param {boolean} enabled + */ void setEnableDebugDrawIKConstraints(bool isEnabled); + /**jsdoc + * @function MyAvatar.setEnableDebugDrawIKChains + * @param {boolean} enabled + */ void setEnableDebugDrawIKChains(bool isEnabled); + /**jsdoc + * @function MyAvatar.setEnableDebugDrawDetailedCollision + * @param {boolean} enabled + */ void setEnableDebugDrawDetailedCollision(bool isEnabled); + /**jsdoc + * Get whether or not your avatar mesh is visible. + * @function MyAvatar.getEnableMeshVisible + * @returns {boolean} true if your avatar's mesh is visible, otherwise false. + */ bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); } + + /**jsdoc + * Set whether or not your avatar mesh is visible. + * @function MyAvatar.setEnableMeshVisible + * @param {boolean} visible - true to set your avatar mesh visible; false to set it invisible. + * @example Make your avatar invisible for 10s. + * MyAvatar.setEnableMeshVisible(false); + * Script.setTimeout(function () { + * MyAvatar.setEnableMeshVisible(true); + * }, 10000); + */ void setEnableMeshVisible(bool isEnabled); + + /**jsdoc + * @function MyAvatar.setEnableInverseKinematics + * @param {boolean} enabled + */ void setEnableInverseKinematics(bool isEnabled); + + /**jsdoc + * @function MyAvatar.getAnimGraphOverrideUrl + * @returns {string} + */ QUrl getAnimGraphOverrideUrl() const; // thread-safe + + /**jsdoc + * @function MyAvatar.setAnimGraphOverrideUrl + * @param {string} url + */ void setAnimGraphOverrideUrl(QUrl value); // thread-safe + + /**jsdoc + * @function MyAvatar.getAnimGraphUrl + * @returns {string} + */ QUrl getAnimGraphUrl() const; // thread-safe + + /**jsdoc + * @function MyAvatar.setAnimGraphUrl + * @param {string} url + */ void setAnimGraphUrl(const QUrl& url); // thread-safe + + /**jsdoc + * @function MyAvatar.getPositionForAudio + * @returns {Vec3} + */ glm::vec3 getPositionForAudio(); + + /**jsdoc + * @function MyAvatar.getOrientationForAudio + * @returns {Quat} + */ glm::quat getOrientationForAudio(); + + /**jsdoc + * @function MyAvatar.setModelScale + * @param {number} scale + */ virtual void setModelScale(float scale) override; signals: + + /**jsdoc + * @function MyAvatar.audioListenerModeChanged + * @returns {Signal} + */ void audioListenerModeChanged(); + + /**jsdoc + * @function MyAvatar.transformChanged + * @returns {Signal} + */ void transformChanged(); + + /**jsdoc + * @function MyAvatar.newCollisionSoundURL + * @param {string} url + * @returns {Signal} + */ void newCollisionSoundURL(const QUrl& url); + + /**jsdoc + * Triggered when the avatar collides with an entity. + * @function MyAvatar.collisionWithEntity + * @param {Collision} collision + * @returns {Signal} + * @example Report each time your avatar collides with an entity. + * MyAvatar.collisionWithEntity.connect(function (collision) { + * print("Your avatar collided with an entity."); + * }); + */ void collisionWithEntity(const Collision& collision); + + /**jsdoc + * @function MyAvatar.energyChanged + * @param {number} energy + * @returns {Signal} + */ void energyChanged(float newEnergy); + + /**jsdoc + * @function MyAvatar.positionGoneTo + * @returns {Signal} + */ + // FIXME: Better name would be goneToLocation(). void positionGoneTo(); + + /**jsdoc + * @function MyAvatar.onLoadComplete + * @returns {Signal} + */ void onLoadComplete(); + + /**jsdoc + * @function MyAvatar.wentAway + * @returns {Signal} + */ void wentAway(); + + /**jsdoc + * @function MyAvatar.wentActive + * @returns {Signal} + */ void wentActive(); + + /**jsdoc + * @function MyAvatar.skeletonChanged + * @returns {Signal} + */ void skeletonChanged(); + + /**jsdoc + * @function MyAvatar.dominantHandChanged + * @param {string} hand + * @returns {Signal} + */ void dominantHandChanged(const QString& hand); + + /**jsdoc + * @function MyAvatar.sensorToWorldScaleChanged + * @param {number} scale + * @returns {Signal} + */ void sensorToWorldScaleChanged(float sensorToWorldScale); + + /**jsdoc + * @function MyAvatar.attachmentsChanged + * @returns {Signal} + */ void attachmentsChanged(); + + /**jsdoc + * @function MyAvatar.scaleChanged + * @returns {Signal} + */ void scaleChanged(); private slots: diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 3e0e4adf18..35e6ca1c92 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -615,9 +615,15 @@ void Wallet::updateImageProvider() { securityImageProvider->setSecurityImage(_securityImage); // inform tablet security image provider - QQmlEngine* tabletEngine = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system")->getTabletSurface()->getSurfaceContext()->engine(); - securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); - securityImageProvider->setSecurityImage(_securityImage); + TabletProxy* tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + if (tablet) { + OffscreenQmlSurface* tabletSurface = tablet->getTabletSurface(); + if (tabletSurface) { + QQmlEngine* tabletEngine = tabletSurface->getSurfaceContext()->engine(); + securityImageProvider = reinterpret_cast(tabletEngine->imageProvider(SecurityImageProvider::PROVIDER_NAME)); + securityImageProvider->setSecurityImage(_securityImage); + } + } } void Wallet::chooseSecurityImage(const QString& filename) { diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h index dfb9c6d638..d4af0bbd37 100644 --- a/interface/src/devices/DdeFaceTracker.h +++ b/interface/src/devices/DdeFaceTracker.h @@ -26,6 +26,11 @@ #include +/**jsdoc + * The FaceTracker API helps manage facial tracking hardware. + * @namespace FaceTracker + */ + class DdeFaceTracker : public FaceTracker, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -57,7 +62,16 @@ public: void setEyeClosingThreshold(float eyeClosingThreshold); public slots: + + /**jsdoc + * @function FaceTracker.setEnabled + * @param {boolean} enabled + */ void setEnabled(bool enabled) override; + + /**jsdoc + * @function FaceTracker.calibrate + */ void calibrate(); private slots: diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 288d3008bb..f2cd9287a5 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -109,7 +109,7 @@ public: * * @typedef {Object} Picks.RayPickResult * @property {number} type The intersection type. - * @property {bool} intersects If there was a valid intersection (type != INTERSECTED_NONE) + * @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE) * @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections. * @property {float} distance The distance to the intersection point from the origin of the ray. * @property {Vec3} intersection The intersection point in world-space. @@ -123,7 +123,7 @@ public: * * @typedef {Object} Picks.StylusPickResult * @property {number} type The intersection type. - * @property {bool} intersects If there was a valid intersection (type != INTERSECTED_NONE) + * @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE) * @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections. * @property {float} distance The distance to the intersection point from the origin of the ray. * @property {Vec3} intersection The intersection point in world-space. diff --git a/interface/src/scripting/AccountServicesScriptingInterface.h b/interface/src/scripting/AccountServicesScriptingInterface.h index cfa51697af..d38a84d8fa 100644 --- a/interface/src/scripting/AccountServicesScriptingInterface.h +++ b/interface/src/scripting/AccountServicesScriptingInterface.h @@ -34,7 +34,17 @@ void DownloadInfoResultFromScriptValue(const QScriptValue& object, DownloadInfoR class AccountServicesScriptingInterface : public QObject { Q_OBJECT - + + /**jsdoc + * The AccountServices API contains helper functions related to user connectivity + * + * @namespace AccountServices + * @property {string} username Read-only. + * @property {boolean} loggedIn Read-only. + * @property {string} findableBy + * @property {string} metaverseServerURL Read-only. + */ + Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged) Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged) Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged) @@ -48,11 +58,33 @@ public: QUrl getMetaverseServerURL() { return DependencyManager::get()->getMetaverseServerURL(); } public slots: + + /**jsdoc + * @function AccountServices.getDownloadInfo + * @returns {DownloadInfoResult} + */ DownloadInfoResult getDownloadInfo(); + + /**jsdoc + * @function AccountServices.updateDownloadInfo + */ void updateDownloadInfo(); + /**jsdoc + * @function AccountServices.isLoggedIn + * @returns {boolean} + */ bool isLoggedIn(); + + /**jsdoc + * @function AccountServices.checkAndSignalForAccessToken + * @returns {boolean} + */ bool checkAndSignalForAccessToken(); + + /**jsdoc + * @function AccountServices.logOut + */ void logOut(); private slots: @@ -66,11 +98,46 @@ private slots: void onUsernameChanged(const QString& username); signals: + + /**jsdoc + * @function AccountServices.connected + * @returns {Signal} + */ void connected(); + + /**jsdoc + * @function AccountServices.disconnected + * @param {string} reason + * @returns {Signal} + */ void disconnected(const QString& reason); + + /**jsdoc + * @function AccountServices.myUsernameChanged + * @param {string} username + * @returns {Signal} + */ void myUsernameChanged(const QString& username); + + /**jsdoc + * @function AccountServices.downloadInfoChanged + * @param {} info + * @returns {Signal} + */ void downloadInfoChanged(DownloadInfoResult info); + + /**jsdoc + * @function AccountServices.findableByChanged + * @param {string} discoverabilityMode + * @returns {Signal} + */ void findableByChanged(const QString& discoverabilityMode); + + /**jsdoc + * @function AccountServices.loggedInChanged + * @param {boolean} loggedIn + * @returns {Signal} + */ void loggedInChanged(bool loggedIn); private: diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 387900b2ae..b27cc344c3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -147,7 +147,7 @@ void Audio::setInputVolume(float volume) { } float Audio::getInputLevel() const { - return resultWithReadLock([&] { + return resultWithReadLock([&] { return _inputLevel; }); } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index b4e63b80c5..c77d1522b5 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -25,6 +25,18 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_OBJECT SINGLETON_DEPENDENCY + /**jsdoc + * The Audio API features tools to help control audio contexts and settings. + * + * @namespace Audio + * @property {boolean} muted + * @property {boolean} noiseReduction + * @property {number} inputVolume + * @property {number} inputLevel Read-only. + * @property {string} context Read-only. + * @property {} devices Read-only. + */ + Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) @@ -49,24 +61,99 @@ public: void showMicMeter(bool show); + /**jsdoc + * @function Audio.setInputDevice + * @param {} device + * @param {boolean} isHMD + */ Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD); - Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD); - Q_INVOKABLE void setReverb(bool enable); - Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); + /**jsdoc + * @function Audio.setOutputDevice + * @param {} device + * @param {boolean} isHMD + */ + Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD); + + /**jsdoc + * @function Audio.setReverb + * @param {boolean} enable + */ + Q_INVOKABLE void setReverb(bool enable); + + /**jsdoc + * @function Audio.setReverbOptions + * @param {} options + */ + Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); + + /**jsdoc + * @function Audio.startRecording + * @param {string} filename + * @returns {boolean} + */ Q_INVOKABLE bool startRecording(const QString& filename); + + /**jsdoc + * @function Audio.stopRecording + */ Q_INVOKABLE void stopRecording(); + + /**jsdoc + * @function Audio.getRecording + * @returns {boolean} + */ Q_INVOKABLE bool getRecording(); signals: + + /**jsdoc + * @function Audio.nop + * @returns {Signal} + */ void nop(); + + /**jsdoc + * @function Audio.mutedChanged + * @param {boolean} isMuted + * @returns {Signal} + */ void mutedChanged(bool isMuted); + + /**jsdoc + * @function Audio.noiseReductionChanged + * @param {boolean} isEnabled + * @returns {Signal} + */ void noiseReductionChanged(bool isEnabled); + + /**jsdoc + * @function Audio.inputVolumeChanged + * @param {number} volume + * @returns {Signal} + */ void inputVolumeChanged(float volume); + + /**jsdoc + * @function Audio.inputLevelChanged + * @param {number} level + * @returns {Signal} + */ void inputLevelChanged(float level); + + /**jsdoc + * @function Audio.contextChanged + * @param {string} context + * @returns {Signal} + */ void contextChanged(const QString& context); public slots: + + /**jsdoc + * @function Audio.onContextChanged + * @returns {Signal} + */ void onContextChanged(); private slots: diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index ee615cde20..a3c80bf1b6 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -108,11 +108,9 @@ AudioDeviceList::~AudioDeviceList() { // store the selected device foreach(std::shared_ptr adevice, _devices) { if (adevice->selectedDesktop) { - qDebug() << "Saving Desktop for" << _mode << "name" << adevice->info.deviceName(); settingDesktop.set(adevice->info.deviceName()); } if (adevice->selectedHMD) { - qDebug() << "Saving HMD for" << _mode << "name" << adevice->info.deviceName(); settingHMD.set(adevice->info.deviceName()); } } @@ -311,7 +309,6 @@ void AudioDeviceList::onDevicesChanged(const QList& devices) { } } - qDebug() << "adding audio device:" << device.display << device.selectedDesktop << device.selectedHMD << _mode; newDevices.push_back(newDevice(device)); } diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index f19caa8478..4fceda3b04 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -28,7 +28,7 @@ class ScriptEngine; /**jsdoc * The Controller API provides facilities to interact with computer and controller hardware. * - *
Functions:
+ *
Functions
* *

Properties

*
    @@ -143,6 +143,61 @@ class ScriptEngine; *
  • {@link Controller.stopInputPlayback|stopInputPlayback}
  • *
* + *
Entity Methods:
+ * + *

The default scripts implement hand controller actions that use {@link Entities.callEntityMethod} to call entity script + * methods, if present in the entity being interacted with.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Method NameDescriptionExample
startFarTrigger
continueFarTrigger
stopFarTrigger
These methods are called when a user is more than 0.3m away from the entity, the entity is triggerable, and the + * user starts, continues, or stops squeezing the trigger.A light switch that can be toggled on and off from a distance.
startNearTrigger
continueNearTrigger
stopNearTrigger
These methods are called when a user is less than 0.3m away from the entity, the entity is triggerable, and the + * user starts, continues, or stops squeezing the trigger.A doorbell that can be rung when a user is near.
startDistanceGrab
continueDistanceGrab
These methods are called when a user is more than 0.3m away from the entity, the entity is either cloneable, or + * grabbable and not locked, and the user starts or continues to squeeze the trigger.A comet that emits icy particle trails when a user is dragging it through the sky.
startNearGrab
continueNearGrab
These methods are called when a user is less than 0.3m away from the entity, the entity is either cloneable, or + * grabbable and not locked, and the user starts or continues to squeeze the trigger.A ball that glows when it's being held close.
releaseGrabThis method is called when a user releases the trigger when having been either distance or near grabbing an + * entity.Turn off the ball glow or comet trail with the user finishes grabbing it.
startEquip
continueEquip
releaseEquip
These methods are called when a user starts, continues, or stops equipping an entity.A glass that stays in the user's hand after the trigger is clicked.
+ *

All the entity methods are called with the following two arguments:

+ *
    + *
  • The entity ID.
  • + *
  • A string, "hand,userID" — where "hand" is "left" or "right", and "userID" + * is the user's {@link MyAvatar|MyAvatar.sessionUUID}.
  • + *
+ * * @namespace Controller * * @property {Controller.Actions} Actions - Predefined actions on Interface and the user's avatar. These can be used as end diff --git a/interface/src/scripting/GooglePolyScriptingInterface.h b/interface/src/scripting/GooglePolyScriptingInterface.h index fdd3597bec..5c37b394fa 100644 --- a/interface/src/scripting/GooglePolyScriptingInterface.h +++ b/interface/src/scripting/GooglePolyScriptingInterface.h @@ -15,6 +15,11 @@ #include #include +/**jsdoc + * The GooglePoly API allows you to interact with Google Poly models direct from inside High Fidelity. + * @namespace GooglePoly + */ + class GooglePolyScriptingInterface : public QObject, public Dependency { Q_OBJECT @@ -22,15 +27,75 @@ public: GooglePolyScriptingInterface(); public slots: + + /**jsdoc + * @function GooglePoly.setAPIKey + * @param {string} key + */ void setAPIKey(const QString& key); + /**jsdoc + * @function GooglePoly.getAssetList + * @param {string} keyword + * @param {string} category + * @param {string} format + * @returns {string} + */ QString getAssetList(const QString& keyword, const QString& category, const QString& format); + + /**jsdoc + * @function GooglePoly.getFBX + * @param {string} keyword + * @param {string} category + * @returns {string} + */ QString getFBX(const QString& keyword, const QString& category); + + /**jsdoc + * @function GooglePoly.getOBJ + * @param {string} keyword + * @param {string} category + * @returns {string} + */ QString getOBJ(const QString& keyword, const QString& category); - QString getBlocks(const QString& keyword, const QString& categoryy); + + /**jsdoc + * @function GooglePoly.getBlocks + * @param {string} keyword + * @param {string} category + * @returns {string} + */ + QString getBlocks(const QString& keyword, const QString& category); + + /**jsdoc + * @function GooglePoly.getGLTF + * @param {string} keyword + * @param {string} category + * @returns {string} + */ QString getGLTF(const QString& keyword, const QString& category); + + /**jsdoc + * @function GooglePoly.getGLTF2 + * @param {string} keyword + * @param {string} category + * @returns {string} + */ QString getGLTF2(const QString& keyword, const QString& category); + + /**jsdoc + * @function GooglePoly.getTilt + * @param {string} keyword + * @param {string} category + * @returns {string} + */ QString getTilt(const QString& keyword, const QString& category); + + /**jsdoc + * @function GooglePoly.getModelInfo + * @param {string} input + * @returns {string} + */ QString getModelInfo(const QString& input); private: diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index 5f762ab511..9716b7e665 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -56,6 +56,19 @@ bool GameplayObjects::removeFromGameplayObjects(const OverlayID& overlayID) { SelectionScriptingInterface::SelectionScriptingInterface() { } +/**jsdoc + * + * + * + * + * + * + * + * + * + *
ValueDescription
"avatar"
"entity"
"overlay"
+ * @typedef {string} Selection.ItemType + */ bool SelectionScriptingInterface::addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id) { if (itemType == "avatar") { return addToGameplayObjects(listName, (QUuid)id); @@ -255,6 +268,12 @@ void SelectionScriptingInterface::printList(const QString& listName) { } } +/**jsdoc + * @typedef {object} Selection.SelectedItemsList + * @property {Uuid[]} avatars - The IDs of the avatars in the selection. + * @property {Uuid[]} entities - The IDs of the entities in the selection. + * @property {Uuid[]} overlays - The IDs of the overlays in the selection. + */ QVariantMap SelectionScriptingInterface::getSelectedItemsList(const QString& listName) const { QReadLocker lock(&_selectionListsLock); QVariantMap list; @@ -461,6 +480,20 @@ bool SelectionHighlightStyle::fromVariantMap(const QVariantMap& properties) { return true; } +/**jsdoc + * @typedef {object} Selection.HighlightStyle + * @property {Color} outlineUnoccludedColor - Color of the specified highlight region. + * @property {Color} outlineOccludedColor - "" + * @property {Color} fillUnoccludedColor- "" + * @property {Color} fillOccludedColor- "" + * @property {number} outlineUnoccludedAlpha - Alpha value ranging from 0.0 (not visible) to 1.0 + * (fully opaque) for the specified highlight region. + * @property {number} outlineOccludedAlpha - "" + * @property {number} fillUnoccludedAlpha - "" + * @property {number} fillOccludedAlpha - "" + * @property {number} outlineWidth - Width of the outline, in pixels. + * @property {boolean} isOutlineSmooth - true to enable outline smooth fall-off. + */ QVariantMap SelectionHighlightStyle::toVariantMap() const { QVariantMap properties; diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index ed6efb39c6..71ff41248a 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -82,6 +82,46 @@ protected: render::HighlightStyle _style; }; +/**jsdoc + * The Selection API provides a means of grouping together avatars, entities, and overlays in named lists. + * @namespace Selection + * + * @example Outline an entity when it is grabbed by a controller. + * // Create a box and copy the following text into the entity's "Script URL" field. + * (function () { + * print("Starting highlight script..............."); + * var _this = this; + * var prevID = 0; + * var listName = "contextOverlayHighlightList"; + * var listType = "entity"; + * + * _this.startNearGrab = function(entityID){ + * if (prevID !== entityID) { + * Selection.addToSelectedItemsList(listName, listType, entityID); + * prevID = entityID; + * } + * }; + * + * _this.releaseGrab = function(entityID){ + * if (prevID !== 0) { + * Selection.removeFromSelectedItemsList("contextOverlayHighlightList", listType, prevID); + * prevID = 0; + * } + * }; + * + * var cleanup = function(){ + * Entities.findEntities(MyAvatar.position, 1000).forEach(function(entity) { + * try { + * Selection.removeListFromMap(listName); + * } catch (e) { + * print("Error cleaning up."); + * } + * }); + * }; + * + * Script.scriptEnding.connect(cleanup); + * }); + */ class SelectionScriptingInterface : public QObject, public Dependency { Q_OBJECT @@ -89,138 +129,120 @@ public: SelectionScriptingInterface(); /**jsdoc - * Query the names of all the selection lists + * Get the names of all the selection lists. * @function Selection.getListNames - * @return An array of names of all the selection lists + * @return {list[]} An array of names of all the selection lists. */ Q_INVOKABLE QStringList getListNames() const; /**jsdoc - * Removes a named selection from the list of selections. + * Delete a named selection list. * @function Selection.removeListFromMap - * @param listName {string} name of the selection - * @returns {bool} true if the selection existed and was successfully removed. + * @param {string} listName - The name of the selection list. + * @returns {boolean} true if the selection existed and was successfully removed, otherwise false. */ Q_INVOKABLE bool removeListFromMap(const QString& listName); /**jsdoc - * Add an item in a selection. + * Add an item to a selection list. * @function Selection.addToSelectedItemsList - * @param listName {string} name of the selection - * @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay") - * @param id {EntityID} the Id of the item to add to the selection - * @returns {bool} true if the item was successfully added. + * @param {string} listName - The name of the selection list to add the item to. + * @param {Selection.ItemType} itemType - The type of the item being added. + * @param {Uuid} id - The ID of the item to add to the selection. + * @returns {boolean} true if the item was successfully added, otherwise false. */ Q_INVOKABLE bool addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id); /**jsdoc - * Remove an item from a selection. + * Remove an item from a selection list. * @function Selection.removeFromSelectedItemsList - * @param listName {string} name of the selection - * @param itemType {string} the type of the item (one of "avatar", "entity" or "overlay") - * @param id {EntityID} the Id of the item to remove - * @returns {bool} true if the item was successfully removed. + * @param {string} listName - The name of the selection list to remove the item from. + * @param {Selection.ItemType} itemType - The type of the item being removed. + * @param {Uuid} id - The ID of the item to remove. + * @returns {boolean} true if the item was successfully removed, otherwise false. + * is returned if the list doesn't contain any data. */ Q_INVOKABLE bool removeFromSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id); /**jsdoc * Remove all items from a selection. * @function Selection.clearSelectedItemsList - * @param listName {string} name of the selection - * @returns {bool} true if the item was successfully cleared. + * @param {string} listName - The name of the selection list. + * @returns {boolean} true if the item was successfully cleared, otherwise false. */ Q_INVOKABLE bool clearSelectedItemsList(const QString& listName); /**jsdoc - * Prints out the list of avatars, entities and overlays stored in a particular selection. + * Print out the list of avatars, entities, and overlays in a selection to the debug log (not the script log). * @function Selection.printList - * @param listName {string} name of the selection + * @param {string} listName - The name of the selection list. */ Q_INVOKABLE void printList(const QString& listName); /**jsdoc - * Query the list of avatars, entities and overlays stored in a particular selection. + * Get the list of avatars, entities, and overlays stored in a selection list. * @function Selection.getList - * @param listName {string} name of the selection - * @return a js object describing the content of a selection list with the following properties: - * - "entities": [ and array of the entityID of the entities in the selection] - * - "avatars": [ and array of the avatarID of the avatars in the selection] - * - "overlays": [ and array of the overlayID of the overlays in the selection] - * If the list name doesn't exist, the function returns an empty js object with no properties. + * @param {string} listName - The name of the selection list. + * @return {Selection.SelectedItemsList} The content of a selection list. If the list name doesn't exist, the function + * returns an empty object with no properties. */ Q_INVOKABLE QVariantMap getSelectedItemsList(const QString& listName) const; /**jsdoc - * Query the names of the highlighted selection lists + * Get the names of the highlighted selection lists. * @function Selection.getHighlightedListNames - * @return An array of names of the selection list currently highlight enabled + * @return {string[]} An array of names of the selection list currently highlight enabled. */ Q_INVOKABLE QStringList getHighlightedListNames() const; /**jsdoc - * Enable highlighting for the named selection. - * If the Selection doesn't exist, it will be created. - * All objects in the list will be displayed with the highlight effect as specified from the highlightStyle. - * The function can be called several times with different values in the style to modify it. - * + * Enable highlighting for a selection list. + * If the selection list doesn't exist, it will be created. + * All objects in the list will be displayed with the highlight effect specified. + * The function can be called several times with different values in the style to modify it.
+ * Note: This function implicitly calls {@link Selection.enableListToScene}. * @function Selection.enableListHighlight - * @param listName {string} name of the selection - * @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle). - * @returns {bool} true if the selection was successfully enabled for highlight. - * - * Note: This function will implicitly call Selection.enableListToScene + * @param {string} listName - The name of the selection list. + * @param {Selection.HighlightStyle} highlightStyle - The highlight style. + * @returns {boolean} true if the selection was successfully enabled for highlight. */ Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle); /**jsdoc - * Disable highlighting for the named selection. - * If the Selection doesn't exist or wasn't enabled for highliting then nothing happens simply returning false. - * + * Disable highlighting for the selection list. + * If the selection list doesn't exist or wasn't enabled for highlighting then nothing happens and false is + * returned.
+ * Note: This function implicitly calls {@link Selection.disableListToScene}. * @function Selection.disableListHighlight - * @param listName {string} name of the selection - * @returns {bool} true if the selection was successfully disabled for highlight, false otherwise. - * - * Note: This function will implicitly call Selection.disableListToScene + * @param {string} listName - The name of the selection list. + * @returns {boolean} true if the selection was successfully disabled for highlight, otherwise + * false. */ Q_INVOKABLE bool disableListHighlight(const QString& listName); /**jsdoc - * Enable scene selection for the named selection. + * Enable scene selection for the selection list. * If the Selection doesn't exist, it will be created. * All objects in the list will be sent to a scene selection. - * * @function Selection.enableListToScene - * @param listName {string} name of the selection - * @returns {bool} true if the selection was successfully enabled on the scene. + * @param {string} listName - The name of the selection list. + * @returns {boolean} true if the selection was successfully enabled on the scene, otherwise false. */ Q_INVOKABLE bool enableListToScene(const QString& listName); + /**jsdoc * Disable scene selection for the named selection. - * If the Selection doesn't exist or wasn't enabled on the scene then nothing happens simply returning false. - * + * If the selection list doesn't exist or wasn't enabled on the scene then nothing happens and false is + * returned. * @function Selection.disableListToScene - * @param listName {string} name of the selection - * @returns {bool} true if the selection was successfully disabled on the scene, false otherwise. + * @param {string} listName - The name of the selection list. + * @returns {boolean} true if the selection was successfully disabled on the scene, false otherwise. */ Q_INVOKABLE bool disableListToScene(const QString& listName); /**jsdoc - * Query the highlight style values for the named selection. - * If the Selection doesn't exist or hasn't been highlight enabled yet, it will return an empty object. - * Otherwise, the jsObject describes the highlight style properties: - * - outlineUnoccludedColor: {xColor} Color of the specified highlight region - * - outlineOccludedColor: {xColor} " - * - fillUnoccludedColor: {xColor} " - * - fillOccludedColor: {xColor} " - * - * - outlineUnoccludedAlpha: {float} Alpha value ranging from 0.0 (not visible) to 1.0 (fully opaque) for the specified highlight region - * - outlineOccludedAlpha: {float} " - * - fillUnoccludedAlpha: {float} " - * - fillOccludedAlpha: {float} " - * - * - outlineWidth: {float} width of the outline expressed in pixels - * - isOutlineSmooth: {bool} true to enable oultine smooth falloff - * + * Get the highlight style values for the a selection list. + * If the selection doesn't exist or hasn't been highlight enabled yet, an empty object is returned. * @function Selection.getListHighlightStyle - * @param listName {string} name of the selection - * @returns {jsObject} highlight style as described above + * @param {string} listName - The name of the selection list. + * @returns {Selection.HighlightStyle} highlight style */ Q_INVOKABLE QVariantMap getListHighlightStyle(const QString& listName) const; @@ -232,6 +254,12 @@ public: void onSelectedItemsListChanged(const QString& listName); signals: + /**jsoc + * Triggered when a list's content changes. + * @function Selection.selectedItemsListChanged + * @param {string} listName - The name of the selection list that changed. + * @returns {Signal} + */ void selectedItemsListChanged(const QString& listName); private: diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 8a8dc61ba8..0b766d2097 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -87,7 +87,7 @@ public slots: * 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. + * @param {string} [message=""] - The message to display. * @example Display a friendly greeting. * Window.alert("Welcome!"); * print("Script continues without waiting"); @@ -98,7 +98,7 @@ public slots: * 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. + * @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?"); @@ -128,8 +128,8 @@ public slots: * 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. + * @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); @@ -144,8 +144,8 @@ public slots: /**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. + * @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); @@ -158,8 +158,8 @@ public slots: * {@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. + * @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); @@ -174,9 +174,9 @@ public slots: /**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 + * @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. @@ -190,9 +190,9 @@ public slots: * {@link Window.browseChanged|browseChanged} 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 + * @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 onBrowseChanged(filename) { @@ -209,9 +209,9 @@ public slots: * 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 + * @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. @@ -226,9 +226,9 @@ public slots: * 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 + * @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) { @@ -245,9 +245,9 @@ public slots: * 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 + * @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. @@ -261,9 +261,9 @@ public slots: * 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 + * @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) { @@ -280,7 +280,7 @@ public slots: * 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. + * @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); @@ -317,14 +317,14 @@ public slots: * NOTE: to provide a non-default value - all previous parameters must be provided. * General > Snapshots. * @function Window.takeSnapshot - * @param {boolean} notify=true - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} + * @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 + * @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 + * @param {number} [aspectRatio=0] - The width/height ratio of the snapshot required. If the value is 0 the * full resolution is used (window dimensions in desktop mode; HMD display dimensions in HMD mode), otherwise one of the * dimensions is adjusted in order to match the aspect ratio. - * @param {string} filename="" - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. + * @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. * If this parameter is "" then the image will be saved as ".jpg". * Otherwise, the image will be saved to this filename, with an appended ".jpg". * @@ -358,7 +358,7 @@ public slots: * Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API. * NOTE: to provide a non-default value - all previous parameters must be provided. * @function Window.takeSecondaryCameraSnapshot - * @param {string} filename="" - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. + * @param {string} [filename=""] - If this parameter is not given, the image will be saved as 'hifi-snap-by--YYYY-MM-DD_HH-MM-SS'. * If this parameter is "" then the image will be saved as ".jpg". * Otherwise, the image will be saved to this filename, with an appended ".jpg". * @@ -397,7 +397,7 @@ public slots: * 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. + * @param {string} [href=""] - The metaverse location where the snapshot was taken. */ void shareSnapshot(const QString& path, const QUrl& href = QUrl("")); diff --git a/interface/src/ui/AddressBarDialog.cpp b/interface/src/ui/AddressBarDialog.cpp index 87bf09a252..789a2a2bdf 100644 --- a/interface/src/ui/AddressBarDialog.cpp +++ b/interface/src/ui/AddressBarDialog.cpp @@ -58,9 +58,8 @@ void AddressBarDialog::loadHome() { qDebug() << "Called LoadHome"; auto locationBookmarks = DependencyManager::get(); QString homeLocation = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK); - const QString DEFAULT_HOME_LOCATION = "localhost"; if (homeLocation == "") { - homeLocation = DEFAULT_HOME_LOCATION; + homeLocation = DEFAULT_HIFI_ADDRESS; } DependencyManager::get()->handleLookupString(homeLocation); } diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index 47f520a639..a9d1509770 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -23,6 +23,15 @@ class AvatarInputs : public QObject { Q_OBJECT HIFI_QML_DECL + /**jsdoc + * API to help manage your Avatar's input + * @namespace AvatarInputs + * @property {boolean} cameraEnabled Read-only. + * @property {boolean} cameraMuted Read-only. + * @property {boolean} isHMD Read-only. + * @property {boolean} showAudioTools + */ + AI_PROPERTY(bool, cameraEnabled, false) AI_PROPERTY(bool, cameraMuted, false) AI_PROPERTY(bool, isHMD, false) @@ -31,22 +40,64 @@ class AvatarInputs : public QObject { public: static AvatarInputs* getInstance(); + + /**jsdoc + * @function AvatarInputs.loudnessToAudioLevel + * @param {number} loudness + * @returns {number} + */ Q_INVOKABLE float loudnessToAudioLevel(float loudness); + AvatarInputs(QObject* parent = nullptr); void update(); bool showAudioTools() const { return _showAudioTools; } public slots: + + /**jsdoc + * @function AvatarInputs.setShowAudioTools + * @param {boolean} showAudioTools + */ void setShowAudioTools(bool showAudioTools); signals: + + /**jsdoc + * @function AvatarInputs.cameraEnabledChanged + * @returns {Signal} + */ void cameraEnabledChanged(); + + /**jsdoc + * @function AvatarInputs.cameraMutedChanged + * @returns {Signal} + */ void cameraMutedChanged(); + + /**jsdoc + * @function AvatarInputs.isHMDChanged + * @returns {Signal} + */ + void isHMDChanged(); + + /**jsdoc + * @function AvatarInputs.showAudioToolsChanged + * @param {boolean} show + * @returns {Signal} + */ void showAudioToolsChanged(bool show); protected: + + /**jsdoc + * @function AvatarInputs.resetSensors + */ Q_INVOKABLE void resetSensors(); + + /**jsdoc + * @function AvatarInputs.toggleCameraMute + */ Q_INVOKABLE void toggleCameraMute(); private: diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 310a4cc1cd..d01e7d6671 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -80,7 +80,6 @@ void DialogsManager::showFeed() { } void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { - qDebug() << "DialogsManager::setDomainConnectionFailureVisibility: visible" << visible; auto tabletScriptingInterface = DependencyManager::get(); auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 10050c94d0..fcdac8c00c 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -65,14 +65,10 @@ const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) { - AbstractViewStateInterface::instance()->postLambdaEvent([surface] { - if (AbstractViewStateInterface::instance()->isAboutToQuit()) { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete surface; - } else { - surface->deleteLater(); - } + AbstractViewStateInterface::instance()->sendLambdaEvent([surface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete surface; }); }; @@ -334,16 +330,20 @@ void Web3DOverlay::render(RenderArgs* args) { renderTransform.setScale(1.0f); batch.setModelTransform(renderTransform); + // Turn off jitter for these entities + batch.pushProjectionJitter(); + auto geometryCache = DependencyManager::get(); if (color.a < OPAQUE_ALPHA_THRESHOLD) { geometryCache->bindWebBrowserProgram(batch, true); } else { geometryCache->bindWebBrowserProgram(batch); } - vec2 halfSize = vec2(size.x, size.y) / 2.0f; geometryCache->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color, _geometryId); + batch.popProjectionJitter(); // Restore jitter batch.setResourceTexture(0, nullptr); // restore default white color after me + } Transform Web3DOverlay::evalRenderTransform() { diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp index 79004a72a6..9777e9c6af 100644 --- a/libraries/animation/src/AnimExpression.cpp +++ b/libraries/animation/src/AnimExpression.cpp @@ -588,6 +588,7 @@ void AnimExpression::evalUnaryMinus(const AnimVariantMap& map, std::stackRead-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + */ + + // Functions are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * Get the list of all resource URLs. + * @function AnimationCache.getResourceList + * @return {string[]} + */ + + /**jsdoc + * @function AnimationCache.dirty + * @returns {Signal} + */ + + /**jsdoc + * @function AnimationCache.updateTotalSize + * @param {number} deltaSize + */ + + /**jsdoc + * @function AnimationCache.prefetch + * @param {string} url + * @param {object} extra + * @returns {object} + */ + + /**jsdoc + * Asynchronously loads a resource from the specified URL and returns it. + * @function AnimationCache.getResource + * @param {string} url - URL of the resource to load. + * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. + * @param {} [extra=null] + * @return {Resource} + */ + + /**jsdoc + * Prefetches a resource. + * @function AnimationCache.prefetch + * @param {string} url - URL of the resource to prefetch. + * @return {Resource} + */ + + + /**jsdoc + * Returns animation resource for particular animation. * @function AnimationCache.getAnimation - * @param url {string} url to load - * @return {Resource} animation + * @param {string} url - URL to load. + * @returns {Resource} animation */ Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); } Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url); diff --git a/libraries/audio-client/src/AudioIOStats.h b/libraries/audio-client/src/AudioIOStats.h index da668da1ac..89db4942ec 100644 --- a/libraries/audio-client/src/AudioIOStats.h +++ b/libraries/audio-client/src/AudioIOStats.h @@ -38,24 +38,137 @@ class MixedProcessedAudioStream; class AudioStreamStatsInterface : public QObject { Q_OBJECT + + /**jsdoc + * @class AudioStats.AudioStreamStats + * @property {number} lossRate Read-only. + * @property {number} lossCount Read-only. + * @property {number} lossRateWindow Read-only. + * @property {number} lossCountWindow Read-only. + * @property {number} framesDesired Read-only. + * @property {number} framesAvailable Read-only. + * @property {number} framesAvailableAvg Read-only. + * @property {number} unplayedMsMax Read-only. + * @property {number} starveCount Read-only. + * @property {number} lastStarveDurationCount Read-only. + * @property {number} dropCount Read-only. + * @property {number} overflowCount Read-only. + * @property {number} timegapMsMax Read-only. + * @property {number} timegapMsAvg Read-only. + * @property {number} timegapMsMaxWindow Read-only. + * @property {number} timegapMsAvgWindow Read-only. + */ + + /**jsdoc + * @function AudioStats.AudioStreamStats.lossRateChanged + * @param {number} lossRate + * @returns {Signal} + */ AUDIO_PROPERTY(float, lossRate) + + /**jsdoc + * @function AudioStats.AudioStreamStats.lossCountChanged + * @param {number} lossCount + * @returns {Signal} + */ AUDIO_PROPERTY(float, lossCount) + + /**jsdoc + * @function AudioStats.AudioStreamStats.lossRateWindowChanged + * @param {number} lossRateWindow + * @returns {Signal} + */ AUDIO_PROPERTY(float, lossRateWindow) + + /**jsdoc + * @function AudioStats.AudioStreamStats.lossCountWindowChanged + * @param {number} lossCountWindow + * @returns {Signal} + */ AUDIO_PROPERTY(float, lossCountWindow) + /**jsdoc + * @function AudioStats.AudioStreamStats.framesDesiredChanged + * @param {number} framesDesired + * @returns {Signal} + */ AUDIO_PROPERTY(int, framesDesired) + + /**jsdoc + * @function AudioStats.AudioStreamStats.framesAvailableChanged + * @param {number} framesAvailable + * @returns {Signal} + */ AUDIO_PROPERTY(int, framesAvailable) + + /**jsdoc + * @function AudioStats.AudioStreamStats.framesAvailableAvgChanged + * @param {number} framesAvailableAvg + * @returns {Signal} + */ AUDIO_PROPERTY(int, framesAvailableAvg) + + /**jsdoc + * @function AudioStats.AudioStreamStats.unplayedMsMaxChanged + * @param {number} unplayedMsMax + * @returns {Signal} + */ AUDIO_PROPERTY(float, unplayedMsMax) + /**jsdoc + * @function AudioStats.AudioStreamStats.starveCountChanged + * @param {number} starveCount + * @returns {Signal} + */ AUDIO_PROPERTY(int, starveCount) + + /**jsdoc + * @function AudioStats.AudioStreamStats.lastStarveDurationCountChanged + * @param {number} lastStarveDurationCount + * @returns {Signal} + */ AUDIO_PROPERTY(int, lastStarveDurationCount) + + /**jsdoc + * @function AudioStats.AudioStreamStats.dropCountChanged + * @param {number} dropCount + * @returns {Signal} + */ AUDIO_PROPERTY(int, dropCount) + + /**jsdoc + * @function AudioStats.AudioStreamStats.overflowCountChanged + * @param {number} overflowCount + * @returns {Signal} + */ AUDIO_PROPERTY(int, overflowCount) + /**jsdoc + * @function AudioStats.AudioStreamStats.timegapMsMaxChanged + * @param {number} timegapMsMax + * @returns {Signal} + */ AUDIO_PROPERTY(quint64, timegapMsMax) + + /**jsdoc + * @function AudioStats.AudioStreamStats.timegapMsAvgChanged + * @param {number} timegapMsAvg + * @returns {Signal} + */ AUDIO_PROPERTY(quint64, timegapMsAvg) + + /**jsdoc + * @function AudioStats.AudioStreamStats.timegapMsMaxWindowChanged + * @param {number} timegapMsMaxWindow + * @returns {Signal} + */ AUDIO_PROPERTY(quint64, timegapMsMaxWindow) + + /**jsdoc + * @function AudioStats.AudioStreamStats.timegapMsAvgWindowChanged + * @param {number} timegapMsAvgWindow + * @returns {Signal} + */ AUDIO_PROPERTY(quint64, timegapMsAvgWindow) public: @@ -68,19 +181,84 @@ private: class AudioStatsInterface : public QObject { Q_OBJECT + + /**jsdoc + * Audio stats from the client. + * @namespace AudioStats + * @property {number} pingMs Read-only. + * @property {number} inputReadMsMax Read-only. + * @property {number} inputUnplayedMsMax Read-only. + * @property {number} outputUnplayedMsMax Read-only. + * @property {number} sentTimegapMsMax Read-only. + * @property {number} sentTimegapMsAvg Read-only. + * @property {number} sentTimegapMsMaxWindow Read-only. + * @property {number} sentTimegapMsAvgWindow Read-only. + * @property {AudioStats.AudioStreamStats} clientStream Read-only. + * @property {AudioStats.AudioStreamStats} mixerStream Read-only. + */ + + /**jsdoc + * @function AudioStats.pingMsChanged + * @param {number} pingMs + * @returns {Signal} + */ AUDIO_PROPERTY(float, pingMs); + + /**jsdoc + * @function AudioStats.inputReadMsMaxChanged + * @param {number} inputReadMsMax + * @returns {Signal} + */ AUDIO_PROPERTY(float, inputReadMsMax); + + /**jsdoc + * @function AudioStats.inputUnplayedMsMaxChanged + * @param {number} inputUnplayedMsMax + * @returns {Signal} + */ AUDIO_PROPERTY(float, inputUnplayedMsMax); + + /**jsdoc + * @function AudioStats.outputUnplayedMsMaxChanged + * @param {number} outputUnplayedMsMax + * @returns {Signal} + */ AUDIO_PROPERTY(float, outputUnplayedMsMax); + + /**jsdoc + * @function AudioStats.sentTimegapMsMaxChanged + * @param {number} sentTimegapMsMax + * @returns {Signal} + */ AUDIO_PROPERTY(quint64, sentTimegapMsMax); + + /**jsdoc + * @function AudioStats.sentTimegapMsAvgChanged + * @param {number} sentTimegapMsAvg + * @returns {Signal} + */ AUDIO_PROPERTY(quint64, sentTimegapMsAvg); + + /**jsdoc + * @function AudioStats.sentTimegapMsMaxWindowChanged + * @param {number} sentTimegapMsMaxWindow + * @returns {Signal} + */ AUDIO_PROPERTY(quint64, sentTimegapMsMaxWindow); + + /**jsdoc + * @function AudioStats.sentTimegapMsAvgWindowChanged + * @param {number} sentTimegapMsAvgWindow + * @returns {Signal} + */ AUDIO_PROPERTY(quint64, sentTimegapMsAvgWindow); Q_PROPERTY(AudioStreamStatsInterface* mixerStream READ getMixerStream NOTIFY mixerStreamChanged); Q_PROPERTY(AudioStreamStatsInterface* clientStream READ getClientStream NOTIFY clientStreamChanged); + + // FIXME: The injectorStreams property isn't available in JavaScript but the notification signal is. Q_PROPERTY(QObject* injectorStreams READ getInjectorStreams NOTIFY injectorStreamsChanged); public: @@ -97,8 +275,23 @@ public: void updateInjectorStreams(const QHash& stats); signals: + + /**jsdoc + * @function AudioStats.mixerStreamChanged + * @returns {Signal} + */ void mixerStreamChanged(); + + /**jsdoc + * @function AudioStats.clientStreamChanged + * @returns {Signal} + */ void clientStreamChanged(); + + /**jsdoc + * @function AudioStats.injectorStreamsChanged + * @returns {Signal} + */ void injectorStreamsChanged(); private: diff --git a/libraries/audio/src/AudioDynamics.h b/libraries/audio/src/AudioDynamics.h index 6f36605025..a43833610a 100644 --- a/libraries/audio/src/AudioDynamics.h +++ b/libraries/audio/src/AudioDynamics.h @@ -10,6 +10,7 @@ // Inline functions to implement audio dynamics processing // +#include #include #include diff --git a/libraries/audio/src/SoundCache.h b/libraries/audio/src/SoundCache.h index bc4ddf303f..d8c52635e0 100644 --- a/libraries/audio/src/SoundCache.h +++ b/libraries/audio/src/SoundCache.h @@ -22,6 +22,67 @@ class SoundCache : public ResourceCache, public Dependency { SINGLETON_DEPENDENCY public: + + // Properties are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * API to manage sound cache resources. + * @namespace SoundCache + * + * @property {number} numTotal - Total number of total resources. Read-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + */ + + + // Functions are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * Get the list of all resource URLs. + * @function SoundCache.getResourceList + * @return {string[]} + */ + + /**jsdoc + * @function SoundCache.dirty + * @returns {Signal} + */ + + /**jsdoc + * @function SoundCache.updateTotalSize + * @param {number} deltaSize + */ + + /**jsdoc + * @function SoundCache.prefetch + * @param {string} url + * @param {object} extra + * @returns {object} + */ + + /**jsdoc + * Asynchronously loads a resource from the specified URL and returns it. + * @function SoundCache.getResource + * @param {string} url - URL of the resource to load. + * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. + * @param {} [extra=null] + * @return {Resource} + */ + + /**jsdoc + * Prefetches a resource. + * @function SoundCache.prefetch + * @param {string} url - URL of the resource to prefetch. + * @return {Resource} + */ + + + /**jsdoc + * @function SoundCache.getSound + * @param {string} url + * @returns {object} + */ Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); protected: virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, diff --git a/libraries/auto-updater/src/AutoUpdater.cpp b/libraries/auto-updater/src/AutoUpdater.cpp index 43563b1d71..e58ac067a6 100644 --- a/libraries/auto-updater/src/AutoUpdater.cpp +++ b/libraries/auto-updater/src/AutoUpdater.cpp @@ -13,6 +13,7 @@ #include #include +#include AutoUpdater::AutoUpdater() { #if defined Q_OS_WIN32 @@ -43,63 +44,114 @@ void AutoUpdater::parseLatestVersionData() { QNetworkReply* sender = qobject_cast(QObject::sender()); QXmlStreamReader xml(sender); + + struct InstallerURLs { + QString full; + QString clientOnly; + }; - int version; + int version { 0 }; QString downloadUrl; QString releaseTime; QString releaseNotes; QString commitSha; QString pullRequestNumber; - while (!xml.atEnd() && !xml.hasError()) { - if (xml.name().toString() == "project" && - xml.attributes().hasAttribute("name") && - xml.attributes().value("name").toString() == "interface") { - xml.readNext(); - - while (!xml.atEnd() && !xml.hasError() && xml.name().toString() != "project") { - if (xml.name().toString() == "platform" && + while (xml.readNextStartElement()) { + if (xml.name() == "projects") { + while (xml.readNextStartElement()) { + if (xml.name().toString() == "project" && xml.attributes().hasAttribute("name") && - xml.attributes().value("name").toString() == _operatingSystem) { - xml.readNext(); - while (!xml.atEnd() && !xml.hasError() && - xml.name().toString() != "platform") { - - if (xml.name().toString() == "build" && xml.tokenType() != QXmlStreamReader::EndElement) { - xml.readNext(); - version = xml.readElementText().toInt(); - xml.readNext(); - downloadUrl = xml.readElementText(); - xml.readNext(); - releaseTime = xml.readElementText(); - xml.readNext(); - if (xml.name().toString() == "notes" && xml.tokenType() != QXmlStreamReader::EndElement) { - xml.readNext(); - while (!xml.atEnd() && !xml.hasError() && xml.name().toString() != "notes") { - if (xml.name().toString() == "note" && xml.tokenType() != QXmlStreamReader::EndElement) { - releaseNotes = releaseNotes + "\n" + xml.readElementText(); + xml.attributes().value("name").toString() == "interface") { + + while (xml.readNextStartElement()) { + + if (xml.name().toString() == "platform" && + xml.attributes().hasAttribute("name") && + xml.attributes().value("name").toString() == _operatingSystem) { + + while (xml.readNextStartElement()) { + if (xml.name() == "build") { + QHash campaignInstallers; + + while (xml.readNextStartElement()) { + if (xml.name() == "version") { + version = xml.readElementText().toInt(); + } else if (xml.name() == "url") { + downloadUrl = xml.readElementText(); + } else if (xml.name() == "installers") { + while (xml.readNextStartElement()) { + QString campaign = xml.name().toString(); + QString full; + QString clientOnly; + while (xml.readNextStartElement()) { + if (xml.name() == "full") { + full = xml.readElementText(); + } else if (xml.name() == "client_only") { + clientOnly = xml.readElementText(); + } else { + xml.skipCurrentElement(); + } + } + campaignInstallers[campaign] = { full, clientOnly }; + } + } else if (xml.name() == "timestamp") { + releaseTime = xml.readElementText(); + } else if (xml.name() == "notes") { + while (xml.readNextStartElement()) { + if (xml.name() == "note") { + releaseNotes = releaseNotes + "\n" + xml.readElementText(); + } else { + xml.skipCurrentElement(); + } + } + } else if (xml.name() == "sha") { + commitSha = xml.readElementText(); + } else if (xml.name() == "pull_request") { + pullRequestNumber = xml.readElementText(); + } else { + xml.skipCurrentElement(); + } } - xml.readNext(); + + static const QString DEFAULT_INSTALLER_CAMPAIGN_NAME = "standard"; + for (auto& campaign : { _installerCampaign, DEFAULT_INSTALLER_CAMPAIGN_NAME }) { + auto it = campaignInstallers.find(campaign); + if (it != campaignInstallers.end()) { + auto& urls = *it; + if (_installerType == InstallerType::CLIENT_ONLY) { + if (!urls.clientOnly.isEmpty()) { + downloadUrl = urls.clientOnly; + break; + } + } else { + if (!urls.full.isEmpty()) { + downloadUrl = urls.full; + break; + } + } + } + } + + appendBuildData(version, downloadUrl, releaseTime, releaseNotes, pullRequestNumber); + releaseNotes = ""; + } else { + xml.skipCurrentElement(); } } - xml.readNext(); - commitSha = xml.readElementText(); - xml.readNext(); - pullRequestNumber = xml.readElementText(); - appendBuildData(version, downloadUrl, releaseTime, releaseNotes, pullRequestNumber); - releaseNotes = ""; + } else { + xml.skipCurrentElement(); } - - xml.readNext(); } + } else { + xml.skipCurrentElement(); } - xml.readNext(); } - } else { - xml.readNext(); + xml.skipCurrentElement(); } } + sender->deleteLater(); emit latestVersionDataParsed(); } diff --git a/libraries/auto-updater/src/AutoUpdater.h b/libraries/auto-updater/src/AutoUpdater.h index 1e62ce0283..f56d7993e9 100644 --- a/libraries/auto-updater/src/AutoUpdater.h +++ b/libraries/auto-updater/src/AutoUpdater.h @@ -36,10 +36,17 @@ class AutoUpdater : public QObject, public Dependency { public: AutoUpdater(); + + enum class InstallerType { + CLIENT_ONLY = 0, + FULL + }; void checkForUpdate(); const QMap>& getBuildData() { return _builds; } void performAutoUpdate(int version); + void setInstallerType(InstallerType type) { _installerType = type; } + void setInstallerCampaign(QString campaign) { _installerCampaign = campaign; } signals: void latestVersionDataParsed(); @@ -49,6 +56,8 @@ signals: private: QMap> _builds; QString _operatingSystem; + InstallerType _installerType { InstallerType::FULL }; + QString _installerCampaign { "" }; void getLatestVersionData(); void downloadUpdateVersion(int version); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index bf0adc5c05..01114b5f6d 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -57,15 +57,7 @@ using AvatarPhysicsCallback = std::function; class Avatar : public AvatarData, public scriptable::ModelProvider { Q_OBJECT - /**jsdoc - * An avatar is representation of yourself or another user. The Avatar API can be used to query or manipulate the avatar of a user. - * NOTE: Avatar extends AvatarData, see those namespace for more properties/methods. - * - * @namespace Avatar - * @augments AvatarData - * - * @property skeletonOffset {Vec3} can be used to apply a translation offset between the avatar's position and the registration point of the 3d model. - */ + // This property has JSDoc in MyAvatar.h. Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset) public: @@ -128,14 +120,25 @@ public: virtual int getJointIndex(const QString& name) const override; virtual QStringList getJointNames() const override; + /**jsdoc + * @function MyAvatar.getDefaultJointRotation + * @param {number} index + * @returns {Quat} + */ Q_INVOKABLE virtual glm::quat getDefaultJointRotation(int index) const; + + /**jsdoc + * @function MyAvatar.getDefaultJointTranslation + * @param {number} index + * @returns {Vec3} + */ Q_INVOKABLE virtual glm::vec3 getDefaultJointTranslation(int index) const; /**jsdoc * Provides read only access to the default joint rotations in avatar coordinates. * The default pose of the avatar is defined by the position and orientation of all bones - * in the avatar's model file. Typically this is a t-pose. - * @function Avatar.getAbsoluteDefaultJointRotationInObjectFrame + * in the avatar's model file. Typically this is a T-pose. + * @function MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame * @param index {number} index number * @returns {Quat} The rotation of this joint in avatar coordinates. */ @@ -144,8 +147,8 @@ public: /**jsdoc * Provides read only access to the default joint translations in avatar coordinates. * The default pose of the avatar is defined by the position and orientation of all bones - * in the avatar's model file. Typically this is a t-pose. - * @function Avatar.getAbsoluteDefaultJointTranslationInObjectFrame + * in the avatar's model file. Typically this is a T-pose. + * @function MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame * @param index {number} index number * @returns {Vec3} The position of this joint in avatar coordinates. */ @@ -170,14 +173,65 @@ public: virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { } + /**jsdoc + * Set the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example, + * with an offset of { x: 0, y: 0.1, z: 0 }, your avatar will appear to be raised off the ground slightly. + * @function MyAvatar.setSkeletonOffset + * @param {Vec3} offset - The skeleton offset to set. + * @example Raise your avatar off the ground a little. + * // Raise your avatar off the ground a little. + * MyAvatar.setSkeletonOffset({ x: 0, y: 0.1: z: 0 }); + * + * // Restore its offset after 5s. + * Script.setTimeout(function () { + * MyAvatar.setSkeletonOffset(Vec3.ZERO); + * }, 5000); + */ Q_INVOKABLE void setSkeletonOffset(const glm::vec3& offset); + + /**jsdoc + * Get the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example, + * with an offset of { x: 0, y: 0.1, z: 0 }, your avatar will appear to be raised off the ground slightly. + * @function MyAvatar.getSkeletonOffset + * @returns {Vec3} The current skeleton offset. + * @example Report your avatar's current skeleton offset. + * print(JSON.stringify(MyAvatar.getSkeletonOffset()); + */ Q_INVOKABLE glm::vec3 getSkeletonOffset() { return _skeletonOffset; } + virtual glm::vec3 getSkeletonPosition() const; + /**jsdoc + * Get the position of a joint in the current avatar. + * @function MyAvatar.getJointPosition + * @param {number} index - The index of the joint. + * @returns {Vec3} The position of the joint in world coordinates. + */ Q_INVOKABLE glm::vec3 getJointPosition(int index) const; + + /**jsdoc + * Get the position of a joint in the current avatar. + * @function MyAvatar.getJointPosition + * @param {string} name - The name of the joint. + * @returns {Vec3} The position of the joint in world coordinates. + * @example Report the position of your avatar's hips. + * print(JSON.stringify(MyAvatar.getJointPosition("Hips"))); + */ Q_INVOKABLE glm::vec3 getJointPosition(const QString& name) const; + + /**jsdoc + * Get the position of the current avatar's neck in world coordinates. + * @function MyAvatar.getNeckPosition + * @returns {Vec3} The position of the neck in world coordinates. + * @example Report the position of your avatar's neck. + * print(JSON.stringify(MyAvatar.getNeckPosition())); + */ Q_INVOKABLE glm::vec3 getNeckPosition() const; + /**jsdoc + * @function MyAvatar.getAcceleration + * @returns {Vec3} + */ Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; } /// Scales a world space position vector relative to the avatar position and scale @@ -201,24 +255,47 @@ public: void setPositionViaScript(const glm::vec3& position) override; void setOrientationViaScript(const glm::quat& orientation) override; - // these call through to the SpatiallyNestable versions, but they are here to expose these to javascript. + + /**jsdoc + * @function MyAvatar.getParentID + * @returns {Uuid} + */ + // This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript. Q_INVOKABLE virtual const QUuid getParentID() const override { return SpatiallyNestable::getParentID(); } + + /**jsdoc + * @function MyAvatar.setParentID + * @param {Uuid} parentID + */ + // This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript. Q_INVOKABLE virtual void setParentID(const QUuid& parentID) override; + + /**jsdoc + * @function MyAvatar.getParentJointIndex + * @returns {number} + */ + // This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript. Q_INVOKABLE virtual quint16 getParentJointIndex() const override { return SpatiallyNestable::getParentJointIndex(); } + + /**jsdoc + * @function MyAvatar.setParentJointIndex + * @param {number} parentJointIndex + */ + // This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript. Q_INVOKABLE virtual void setParentJointIndex(quint16 parentJointIndex) override; - /**jsdoc - * Information about a single joint in an Avatar's skeleton hierarchy. - * @typedef Avatar.SkeletonJoint - * @property {string} name - name of joint - * @property {number} index - joint index - * @property {number} parentIndex - index of this joint's parent (-1 if no parent) - */ /**jsdoc - * Returns an array of joints, where each joint is an object containing name, index and parentIndex fields. - * @function Avatar.getSkeleton - * @returns {Avatar.SkeletonJoint[]} returns a list of information about each joint in this avatar's skeleton. + * Returns an array of joints, where each joint is an object containing name, index, and parentIndex fields. + * @function MyAvatar.getSkeleton + * @returns {MyAvatar.SkeletonJoint[]} A list of information about each joint in this avatar's skeleton. + */ + /**jsdoc + * Information about a single joint in an Avatar's skeleton hierarchy. + * @typedef MyAvatar.SkeletonJoint + * @property {string} name - Joint name. + * @property {number} index - Joint index. + * @property {number} parentIndex - Index of this joint's parent (-1 if no parent). */ Q_INVOKABLE QList getSkeleton(); @@ -235,6 +312,11 @@ public: void setTargetScale(float targetScale) override; float getTargetScale() const { return _targetScale; } + /**jsdoc + * @function MyAvatar.getSimulationRate + * @param {string} [rateName=""] + * @returns {number} + */ Q_INVOKABLE float getSimulationRate(const QString& rateName = QString("")) const; bool hasNewJointData() const { return _hasNewJointData; } @@ -256,6 +338,7 @@ public: bool isFading() const { return _isFading; } void updateFadingStatus(render::ScenePointer scene); + // JSDoc is in AvatarData.h. Q_INVOKABLE virtual float getEyeHeight() const override; // returns eye height of avatar in meters, ignoring avatar scale. @@ -282,16 +365,57 @@ public slots: // FIXME - these should be migrated to use Pose data instead // thread safe, will return last valid palm from cache + + /**jsdoc + * Get the position of the left palm in world coordinates. + * @function MyAvatar.getLeftPalmPosition + * @returns {Vec3} The position of the left palm in world coordinates. + * @example Report the position of your avatar's left palm. + * print(JSON.stringify(MyAvatar.getLeftPalmPosition())); + */ glm::vec3 getLeftPalmPosition() const; + + /**jsdoc + * Get the rotation of the left palm in world coordinates. + * @function MyAvatar.getLeftPalmRotation + * @returns {Vec3} The rotation of the left palm in world coordinates. + * @example Report the rotation of your avatar's left palm. + * print(JSON.stringify(MyAvatar.getLeftPalmRotation())); + */ glm::quat getLeftPalmRotation() const; + /**jsdoc + * Get the position of the right palm in world coordinates. + * @function MyAvatar.getRightPalmPosition + * @returns {Vec3} The position of the right palm in world coordinates. + * @example Report the position of your avatar's right palm. + * print(JSON.stringify(MyAvatar.getRightPalmPosition())); + */ glm::vec3 getRightPalmPosition() const; + + /**jsdoc + * Get the rotation of the right palm in world coordinates. + * @function MyAvatar.getRightPalmRotation + * @returns {Vec3} The rotation of the right palm in world coordinates. + * @example Report the rotation of your avatar's right palm. + * print(JSON.stringify(MyAvatar.getRightPalmRotation())); + */ glm::quat getRightPalmRotation() const; // hooked up to Model::setURLFinished signal void setModelURLFinished(bool success); - // hooked up to Model::rigReady & rigReset signals + /**jsdoc + * @function MyAvatar.rigReady + * @returns {Signal} + */ + // Hooked up to Model::rigReady signal void rigReady(); + + /**jsdoc + * @function MyAvatar.rigReset + * @returns {Signal} + */ + // Jooked up to Model::rigReset signal void rigReset(); protected: diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 6b974ac9ae..db38999481 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2362,6 +2362,15 @@ glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const return glm::vec3(); } +/**jsdoc + * @typedef MyAvatar.AttachmentData + * @property {string} modelUrl + * @property {string} jointName + * @property {Vec3} translation + * @property {Vec3} rotation + * @property {number} scale + * @property {boolean} soft + */ QVariant AttachmentData::toVariant() const { QVariantMap result; result["modelUrl"] = modelURL; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 7c188019db..888c3bfb24 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -353,6 +353,7 @@ public: class AvatarData : public QObject, public SpatiallyNestable { Q_OBJECT + // The following properties have JSDoc in MyAvatar.h. Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript) Q_PROPERTY(float scale READ getTargetScale WRITE setTargetScale) Q_PROPERTY(float density READ getDensity) @@ -505,7 +506,7 @@ public: /**jsdoc * returns the minimum scale allowed for this avatar in the current domain. * This value can change as the user changes avatars or when changing domains. - * @function AvatarData.getDomainMinScale + * @function MyAvatar.getDomainMinScale * @returns {number} minimum scale allowed for this avatar in the current domain. */ Q_INVOKABLE float getDomainMinScale() const; @@ -513,7 +514,7 @@ public: /**jsdoc * returns the maximum scale allowed for this avatar in the current domain. * This value can change as the user changes avatars or when changing domains. - * @function AvatarData.getDomainMaxScale + * @function MyAvatar.getDomainMaxScale * @returns {number} maximum scale allowed for this avatar in the current domain. */ Q_INVOKABLE float getDomainMaxScale() const; @@ -529,16 +530,16 @@ public: /**jsdoc * Provides read only access to the current eye height of the avatar. * This height is only an estimate and might be incorrect for avatars that are missing standard joints. - * @function AvatarData.getEyeHeight - * @returns {number} eye height of avatar in meters + * @function MyAvatar.getEyeHeight + * @returns {number} Eye height of avatar in meters. */ Q_INVOKABLE virtual float getEyeHeight() const { return _targetScale * getUnscaledEyeHeight(); } /**jsdoc * Provides read only access to the current height of the avatar. * This height is only an estimate and might be incorrect for avatars that are missing standard joints. - * @function AvatarData.getHeight - * @returns {number} height of avatar in meters + * @function MyAvatar.getHeight + * @returns {number} Height of avatar in meters. */ Q_INVOKABLE virtual float getHeight() const; @@ -547,49 +548,372 @@ public: void setDomainMinimumHeight(float domainMinimumHeight); void setDomainMaximumHeight(float domainMaximumHeight); - // Hand State + /**jsdoc + * @function MyAvatar.setHandState + * @param {string} state + */ Q_INVOKABLE void setHandState(char s) { _handState = s; } + + /**jsdoc + * @function MyAvatar.getHandState + * @returns {string} + */ Q_INVOKABLE char getHandState() const { return _handState; } const QVector& getRawJointData() const { return _jointData; } + + /**jsdoc + * @function MyAvatar.setRawJointData + * @param {JointData[]} data + */ Q_INVOKABLE void setRawJointData(QVector data); + /**jsdoc + * Set a specific joint's rotation and position relative to its parent. + *

Setting joint data completely overrides/replaces all motion from the default animation system including inverse + * kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints, + * the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate + * joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set + * the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.

+ * @function MyAvatar.setJointData + * @param {number} index - The index of the joint. + * @param {Quat} rotation - The rotation of the joint relative to its parent. + * @param {Vec3} translation - The translation of the joint relative to its parent. + * @example Set your avatar to it's default T-pose for a while.
+ * Avatar in T-pose + * + * // Set all joint translations and rotations to defaults. + * var i, length, rotation, translation; + * for (i = 0, length = MyAvatar.getJointNames().length; i < length; i++) { + * rotation = MyAvatar.getDefaultJointRotation(i); + * translation = MyAvatar.getDefaultJointTranslation(i); + * MyAvatar.setJointData(i, rotation, translation); + * } + * + * // Restore your avatar's motion after 5s. + * Script.setTimeout(function () { + * MyAvatar.clearJointsData(); + * }, 5000); + */ Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation); + + /**jsdoc + * Set a specific joint's rotation relative to its parent. + *

Setting joint data completely overrides/replaces all motion from the default animation system including inverse + * kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints, + * the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate + * joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set + * the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.

+ * @function MyAvatar.setJointRotation + * @param {number} index - The index of the joint. + * @param {Quat} rotation - The rotation of the joint relative to its parent. + */ Q_INVOKABLE virtual void setJointRotation(int index, const glm::quat& rotation); + + /**jsdoc + * Set a specific joint's translation relative to its parent. + *

Setting joint data completely overrides/replaces all motion from the default animation system including inverse + * kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints, + * the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate + * joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set + * the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.

+ * @function MyAvatar.setJointTranslation + * @param {number} index - The index of the joint. + * @param {Vec3} translation - The translation of the joint relative to its parent. + */ Q_INVOKABLE virtual void setJointTranslation(int index, const glm::vec3& translation); + + /**jsdoc + * Clear joint translations and rotations set by script for a specific joint. This restores all motion from the default + * animation system including inverse kinematics for that joint. + *

Note: This is slightly faster than the function variation that specifies the joint name.

+ * @function MyAvatar.clearJointData + * @param {number} index - The index of the joint. + */ Q_INVOKABLE virtual void clearJointData(int index); + + /**jsdoc + * @function MyAvatar.isJointDataValid + * @param {number} index + * @returns {boolean} + */ Q_INVOKABLE bool isJointDataValid(int index) const; + + /**jsdoc + * Get the rotation of a joint relative to its parent. For information on the joint hierarchy used, see + * Avatar Standards. + * @function MyAvatar.getJointRotation + * @param {number} index - The index of the joint. + * @returns {Quat} The rotation of the joint relative to its parent. + */ Q_INVOKABLE virtual glm::quat getJointRotation(int index) const; + + /**jsdoc + * Get the translation of a joint relative to its parent. For information on the joint hierarchy used, see + * Avatar Standards. + * @function MyAvatar.getJointTranslation + * @param {number} index - The index of the joint. + * @returns {Vec3} The translation of the joint relative to its parent. + */ Q_INVOKABLE virtual glm::vec3 getJointTranslation(int index) const; + /**jsdoc + * Set a specific joint's rotation and position relative to its parent. + *

Setting joint data completely overrides/replaces all motion from the default animation system including inverse + * kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints, + * the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate + * joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set + * the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.

+ * @function MyAvatar.setJointData + * @param {string} name - The name of the joint. + * @param {Quat} rotation - The rotation of the joint relative to its parent. + * @param {Vec3} translation - The translation of the joint relative to its parent. + */ Q_INVOKABLE virtual void setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation); + + /**jsdoc + * Set a specific joint's rotation relative to its parent. + *

Setting joint data completely overrides/replaces all motion from the default animation system including inverse + * kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints, + * the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate + * joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set + * the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.

+ * @function MyAvatar.setJointRotation + * @param {string} name - The name of the joint. + * @param {Quat} rotation - The rotation of the joint relative to its parent. + * @example Set your avatar to its default T-pose then rotate its right arm.
+ * Avatar in T-pose with arm rotated + * // Set all joint translations and rotations to defaults. + * var i, length, rotation, translation; + * for (i = 0, length = MyAvatar.getJointNames().length; i < length; i++) { + * rotation = MyAvatar.getDefaultJointRotation(i); + * translation = MyAvatar.getDefaultJointTranslation(i); + * MyAvatar.setJointData(i, rotation, translation); + * } + * + * // Rotate the right arm. + * var newArmRotation = { x: 0.47, y: 0.22, z: -0.02, w: 0.87 }; + * MyAvatar.setJointRotation("RightArm", newArmRotation); + * + * // Restore your avatar's motion after 5s. + * Script.setTimeout(function () { + * MyAvatar.clearJointsData(); + * }, 5000); + */ Q_INVOKABLE virtual void setJointRotation(const QString& name, const glm::quat& rotation); + + /**jsdoc + * Set a specific joint's translation relative to its parent. + *

Setting joint data completely overrides/replaces all motion from the default animation system including inverse + * kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints, + * the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate + * joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set + * the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.

+ * @function MyAvatar.setJointTranslation + * @param {string} name - The name of the joint. + * @param {Vec3} translation - The translation of the joint relative to its parent. + * @example Stretch your avatar's neck. Depending on the avatar you are using, you will either see a gap between + * the head and body or you will see the neck stretched.
+ * Avatar with neck stretched + * // Stretch your avatar's neck. + * MyAvatar.setJointTranslation("Neck", { x: 0, y: 25, z: 0 }); + * + * // Restore your avatar's neck after 5s. + * Script.setTimeout(function () { + * MyAvatar.clearJointData("Neck"); + * }, 5000); + */ Q_INVOKABLE virtual void setJointTranslation(const QString& name, const glm::vec3& translation); + + /**jsdoc + * Clear joint translations and rotations set by script for a specific joint. This restores all motion from the default + * animation system including inverse kinematics for that joint. + *

Note: This is slightly slower than the function variation that specifies the joint index.

+ * @function MyAvatar.clearJointData + * @param {string} name - The name of the joint. + * @example Offset and restore the position of your avatar's head. + * // Move your avatar's head up by 25cm from where it should be. + * MyAvatar.setJointTranslation("Neck", { x: 0, y: 0.25, z: 0 }); + * + * // Restore your avatar's head to its default position after 5s. + * Script.setTimeout(function () { + * MyAvatar.clearJointData("Neck"); + * }, 5000); + */ Q_INVOKABLE virtual void clearJointData(const QString& name); + + /**jsdoc + * @function MyAvatar.isJointDataValid + * @param {string} name + * @returns {boolean} + */ Q_INVOKABLE virtual bool isJointDataValid(const QString& name) const; + + /**jsdoc + * Get the rotation of a joint relative to its parent. For information on the joint hierarchy used, see + * Avatar Standards. + * @function MyAvatar.getJointRotation + * @param {string} name - The name of the joint. + * @returns {Quat} The rotation of the joint relative to its parent. + * @example Report the rotation of your avatar's hips joint. + * print(JSON.stringify(MyAvatar.getJointRotation("Hips"))); + */ Q_INVOKABLE virtual glm::quat getJointRotation(const QString& name) const; + + /**jsdoc + * Get the translation of a joint relative to its parent. For information on the joint hierarchy used, see + * Avatar Standards. + * @function MyAvatar.getJointTranslation + * @param {number} name - The name of the joint. + * @returns {Vec3} The translation of the joint relative to its parent. + * @example Report the translation of your avatar's hips joint. + * print(JSON.stringify(MyAvatar.getJointRotation("Hips"))); + */ Q_INVOKABLE virtual glm::vec3 getJointTranslation(const QString& name) const; + /**jsdoc + * Get the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint. + * @function MyAvatar.getJointRotations + * @returns {Quat[]} The rotations of all joints relative to each's parent. The values are in the same order as the array + * returned by {@link MyAvatar.getJointNames}. + * @example Report the rotations of all your avatar's joints. + * print(JSON.stringify(MyAvatar.getJointRotations())); + */ Q_INVOKABLE virtual QVector getJointRotations() const; + + /**jsdoc + * @function MyAvatar.getJointTranslations + * @returns {Vec3[]} + */ Q_INVOKABLE virtual QVector getJointTranslations() const; + + /**jsdoc + * Set the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint. + *

Setting joint data completely overrides/replaces all motion from the default animation system including inverse + * kinematics, but just for the specified joint. So for example, if you were to procedurally manipulate the finger joints, + * the avatar's hand and head would still do inverse kinematics properly. However, as soon as you start to manipulate + * joints in the inverse kinematics chain, the inverse kinematics might not function as you expect. For example, if you set + * the rotation of the elbow, the hand inverse kinematics position won't end up in the right place.

+ * @function MyAvatar.setJointRotations + * @param {Quat[]} jointRotations - The rotations for all joints in the avatar. The values are in the same order as the + * array returned by {@link MyAvatar.getJointNames}. + * @example Set your avatar to its default T-pose then rotate its right arm.
+ * Avatar in T-pose + * + * // Set all joint translations and rotations to defaults. + * var i, length, rotation, translation; + * for (i = 0, length = MyAvatar.getJointNames().length; i < length; i++) { + * rotation = MyAvatar.getDefaultJointRotation(i); + * translation = MyAvatar.getDefaultJointTranslation(i); + * MyAvatar.setJointData(i, rotation, translation); + * } + * + * // Get all join rotations. + * var jointRotations = MyAvatar.getJointRotations(); + * + * // Update the rotation of the right arm in the array. + * jointRotations[MyAvatar.getJointIndex("RightArm")] = { x: 0.47, y: 0.22, z: -0.02, w: 0.87 }; + * + * // Update all joint rotations. + * MyAvatar.setJointRotations(jointRotations); + * + * // Restore your avatar's motion after 5s. + * Script.setTimeout(function () { + * MyAvatar.clearJointsData(); + * }, 5000); + */ Q_INVOKABLE virtual void setJointRotations(const QVector& jointRotations); + + /**jsdoc + * @function MyAvatar.setJointTranslations + * @param {Vec3[]} translations + */ Q_INVOKABLE virtual void setJointTranslations(const QVector& jointTranslations); + /**jsdoc + * Clear all joint translations and rotations that have been set by script. This restores all motion from the default + * animation system including inverse kinematics for all joints. + * @function MyAvatar.clearJointsData + * @example Set your avatar to it's default T-pose for a while. + * // Set all joint translations and rotations to defaults. + * var i, length, rotation, translation; + * for (i = 0, length = MyAvatar.getJointNames().length; i < length; i++) { + * rotation = MyAvatar.getDefaultJointRotation(i); + * translation = MyAvatar.getDefaultJointTranslation(i); + * MyAvatar.setJointData(i, rotation, translation); + * } + * + * // Restore your avatar's motion after 5s. + * Script.setTimeout(function () { + * MyAvatar.clearJointsData(); + * }, 5000); + */ Q_INVOKABLE virtual void clearJointsData(); + /**jsdoc + * Get the joint index for a named joint. The joint index value is the position of the joint in the array returned by + * {@link MyAvatar.getJointNames}. + * @function MyAvatar.getJointIndex + * @param {string} name - The name of the joint. + * @returns {number} The index of the joint. + * @example Report the index of your avatar's left arm joint. + * print(JSON.stringify(MyAvatar.getJointIndex("LeftArm")); + */ /// Returns the index of the joint with the specified name, or -1 if not found/unknown. Q_INVOKABLE virtual int getJointIndex(const QString& name) const; + /**jsdoc + * Get the names of all the joints in the current avatar. + * @function MyAvatar.getJointNames + * @returns {string[]} The joint names. + * @example Report the names of all the joints in your current avatar. + * print(JSON.stringify(MyAvatar.getJointNames())); + */ Q_INVOKABLE virtual QStringList getJointNames() const; + + /**jsdoc + * @function MyAvatar.setBlendshape + * @param {string} name + * @param {number} value + */ Q_INVOKABLE void setBlendshape(QString name, float val) { _headData->setBlendshape(name, val); } + + /**jsdoc + * @function MyAvatar.getAttachmentsVariant + * @returns {object} + */ + // FIXME: Can this name be improved? Can it be deprecated? Q_INVOKABLE QVariantList getAttachmentsVariant() const; + + /**jsdoc + * @function MyAvatar.setAttachmentsVariant + * @param {object} variant + */ + // FIXME: Can this name be improved? Can it be deprecated? Q_INVOKABLE void setAttachmentsVariant(const QVariantList& variant); + + /**jsdoc + * @function MyAvatar.updateAvatarEntity + * @param {Uuid} entityID + * @param {string} entityData + */ Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); + /**jsdoc + * @function MyAvatar.clearAvatarEntity + * @param {Uuid} entityID + */ Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID); + + /**jsdoc + * @function MyAvatar.setForceFaceTrackerConnected + * @param {boolean} connected + */ Q_INVOKABLE void setForceFaceTrackerConnected(bool connected) { _forceFaceTrackerConnected = connected; } // key state @@ -627,15 +951,96 @@ public: markIdentityDataChanged(); } + /**jsdoc + * Get information about all models currently attached to your avatar. + * @function MyAvatar.getAttachmentData + * @returns {MyAvatar.AttachmentData[]} Information about all models attached to your avatar. + * @example Report the URLs of all current attachments. + * var attachments = MyAvatar.getaAttachmentData(); + * for (var i = 0; i < attachments.length; i++) { + * print (attachments[i].modelURL); + * } + */ Q_INVOKABLE QVector getAttachmentData() const; + + /**jsdoc + * Set all models currently attached to your avatar. For example, if you retrieve attachment data using + * {@link MyAvatar.getAttachmentData}, make changes to it, and then want to update your avatar's attachments per the + * changed data. You can also remove all attachments by using setting attachmentData to null. + * @function MyAvatar.setAttachmentData + * @param {MyAvatar.AttachmentData[]} attachmentData - The attachment data defining the models to have attached to your avatar. Use + * null to remove all attachments. + * @example Remove a hat attachment if your avatar is wearing it. + * var hatURL = "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx"; + * var attachments = MyAvatar.getAttachmentData(); + * + * for (var i = 0; i < attachments.length; i++) { + * if (attachments[i].modelURL === hatURL) { + * attachments.splice(i, 1); + * MyAvatar.setAttachmentData(attachments); + * break; + * } + * } + */ Q_INVOKABLE virtual void setAttachmentData(const QVector& attachmentData); + /**jsdoc + * Attach a model to your avatar. For example, you can give your avatar a hat to wear, a guitar to hold, or a surfboard to + * stand on. + *

Note: Attached models are models only; they are not entities and can not be manipulated using the {@link Entities} API. + * Nor can you use this function to attach an entity (such as a sphere or a box) to your avatar.

+ * @function MyAvatar.attach + * @param {string} modelURL - The URL of the model to attach. Models can be .FBX or .OBJ format. + * @param {string} [jointName=""] - The name of the avatar joint (see {@link MyAvatar.getJointNames}) to attach the model + * to. + * @param {Vec3} [translation=Vec3.ZERO] - The offset to apply to the model relative to the joint position. + * @param {Quat} [rotation=Quat.IDENTITY] - The rotation to apply to the model relative to the joint orientation. + * @param {number} [scale=1.0] - The scale to apply to the model. + * @param {boolean} [isSoft=false] - If the model has a skeleton, set this to true so that the bones of the + * attached model's skeleton are be rotated to fit the avatar's current pose. isSoft is used, for example, + * to have clothing that moves with the avatar.
+ * If true, the translation, rotation, and scale parameters are + * ignored. + * @param {boolean} [allowDuplicates=false] + * @param {boolean} [useSaved=true] + * @example Attach a cowboy hat to your avatar's head. + * var attachment = { + * modelURL: "https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx", + * jointName: "Head", + * translation: {"x": 0, "y": 0.25, "z": 0}, + * rotation: {"x": 0, "y": 0, "z": 0, "w": 1}, + * scale: 1, + * isSoft: false + * }; + * + * MyAvatar.attach(attachment.modelURL, + * attachment.jointName, + * attachment.translation, + * attachment.rotation, + * attachment.scale, + * attachment.isSoft); + */ Q_INVOKABLE virtual void attach(const QString& modelURL, const QString& jointName = QString(), const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, bool isSoft = false, bool allowDuplicates = false, bool useSaved = true); + /**jsdoc + * Detach the most recently attached instance of a particular model from either a specific joint or any joint. + * @function MyAvatar.detachOne + * @param {string} modelURL - The URL of the model to detach. + * @param {string} [jointName=""] - The name of the joint to detach the model from. If "", then the most + * recently attached model is removed from which ever joint it was attached to. + */ Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString()); + + /**jsdoc + * Detach all instances of a particular model from either a specific joint or all joints. + * @function MyAvatar.detachAll + * @param {string} modelURL - The URL of the model to detach. + * @param {string} [jointName=""] - The name of the joint to detach the model from. If "", then the model is + * detached from all joints. + */ Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString()); QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); } @@ -657,19 +1062,63 @@ public: glm::vec3 getClientGlobalPosition() const { return _globalPosition; } glm::vec3 getGlobalBoundingBoxCorner() const { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; } + /**jsdoc + * @function MyAvatar.getAvatarEntityData + * @returns {object} + */ Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; + + /**jsdoc + * @function MyAvatar.setAvatarEntityData + * @param {object} avatarEntityData + */ Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); + virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } void insertDetachedEntityID(const QUuid entityID); AvatarEntityIDs getAndClearRecentlyDetachedIDs(); + /**jsdoc + * @function MyAvatar.getSensorToWorldMatrix + * @returns {Mat4} + */ // thread safe Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const; + + /**jsdoc + * @function MyAvatar.getSensorToWorldScale + * @returns {number} + */ + // thread safe Q_INVOKABLE float getSensorToWorldScale() const; + + /**jsdoc + * @function MyAvatar.getControllerLeftHandMatrix + * @returns {Mat4} + */ + // thread safe Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const; + + /**jsdoc + * @function MyAvatar.getControllerRightHandMatrix + * @returns {Mat4} + */ + // thread safe Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const; + + /**jsdoc + * @function MyAvatar.getDataRate + * @param {string} [rateName=""] + * @returns {number} + */ Q_INVOKABLE float getDataRate(const QString& rateName = QString("")) const; + + /**jsdoc + * @function MyAvatar.getUpdateRate + * @param {string} [rateName=""] + * @returns {number} + */ Q_INVOKABLE float getUpdateRate(const QString& rateName = QString("")) const; int getJointCount() const { return _jointData.size(); } @@ -705,17 +1154,60 @@ public: virtual void removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) {} signals: + + /**jsdoc + * @function MyAvatar.displayNameChanged + * @returns {Signal} + */ void displayNameChanged(); + + /**jsdoc + * @function MyAvatar.sessionDisplayNameChanged + * @returns {Signal} + */ void sessionDisplayNameChanged(); + + /**jsdoc + * @function MyAvatar.skeletonModelURLChanged + * @returns {Signal} + */ void skeletonModelURLChanged(); + + /**jsdoc + * @function MyAvatar.lookAtSnappingChanged + * @param {boolean} enabled + * @returns {Signal} + */ void lookAtSnappingChanged(bool enabled); + + /**jsdoc + * @function MyAvatar.sessionUUIDChanged + * @returns {Signal} + */ void sessionUUIDChanged(); public slots: + +/**jsdoc + * @function MyAvatar.sendAvatarDataPacket + * @param {boolean} [sendAll=false] + */ void sendAvatarDataPacket(bool sendAll = false); + + /**jsdoc + * @function MyAvatar.sendIdentityPacket + */ void sendIdentityPacket(); + /**jsdoc + * @function MyAvatar.setJointMappingsFromNetworkReply + */ void setJointMappingsFromNetworkReply(); + + /**jsdoc + * @function MyAvatar.setSessionUUID + * @param {Uuid} sessionUUID + */ virtual void setSessionUUID(const QUuid& sessionUUID) { if (sessionUUID != getID()) { if (sessionUUID == QUuid()) { @@ -727,13 +1219,45 @@ public slots: } } + /**jsdoc + * @function MyAvatar.getAbsoluteJointRotationInObjectFrame + * @param {number} index + * @returns {Quat} + */ virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; + + /**jsdoc + * @function MyAvatar.getAbsoluteJointTranslationInObjectFrame + * @param {number} index + * @returns {Vec3} + */ virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + + /**jsdoc + * @function MyAvatar.setAbsoluteJointRotationInObjectFrame + * @param {number} index + * @param {Quat} rotation + * @returns {boolean} + */ virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } + + /**jsdoc + * @function MyAvatar.setAbsoluteJointTranslationInObjectFrame + * @param {number} index + * @param {Vec3} translation + * @returns {boolean} + */ virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } + /**jsdoc + * @function MyAvatar.getTargetScale + * @returns {number} + */ float getTargetScale() const { return _targetScale; } // why is this a slot? + /**jsdoc + * @function MyAvatar.resetLastSent + */ void resetLastSent() { _lastToByteArray = 0; } protected: diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index e4a485028f..dc3f40c5d3 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + #ifndef hifi_AvatarHashMap_h #define hifi_AvatarHashMap_h @@ -39,9 +40,22 @@ public: int size() { return _avatarHash.size(); } // Currently, your own avatar will be included as the null avatar id. + + /**jsdoc + * @function AvatarManager.getAvatarIdentifiers + * @returns {Uuid[]} + */ Q_INVOKABLE QVector getAvatarIdentifiers(); + + /**jsdoc + * @function AvatarManager.getAvatarsInRange + * @param {Vec3} position + * @param {number} range + * @returns {Uuid[]} + */ Q_INVOKABLE QVector getAvatarsInRange(const glm::vec3& position, float rangeMeters) const; + // No JSDod because it's documwented in AvatarManager. // Null/Default-constructed QUuids will return MyAvatar Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); } @@ -49,18 +63,67 @@ public: int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters); signals: + + /**jsdoc + * @function AvatarManager.avatarAddedEvent + * @param {Uuid} sessionUUID + * @returns {Signal} + */ void avatarAddedEvent(const QUuid& sessionUUID); + + /**jsdoc + * @function AvatarManager.avatarRemovedEvent + * @param {Uuid} sessionUUID + * @returns {Signal} + */ void avatarRemovedEvent(const QUuid& sessionUUID); + + /**jsdoc + * @function AvatarManager.avatarSessionChangedEvent + * @param {Uuid} sessionUUID + * @param {Uuid} oldSessionUUID + * @returns {Signal} + */ void avatarSessionChangedEvent(const QUuid& sessionUUID,const QUuid& oldUUID); public slots: + + /**jsdoc + * @function AvatarManager.isAvatarInRange + * @param {string} position + * @param {string} range + * @returns {boolean} + */ bool isAvatarInRange(const glm::vec3 & position, const float range); protected slots: + + /**jsdoc + * @function AvatarManager.sessionUUIDChanged + * @param {Uuid} sessionUUID + * @param {Uuid} oldSessionUUID + */ void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID); + /**jsdoc + * @function AvatarManager.processAvatarDataPacket + * @param {} message + * @param {} sendingNode + */ void processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode); + + /**jsdoc + * @function AvatarManager.processAvatarIdentityPacket + * @param {} message + * @param {} sendingNode + */ void processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode); + + /**jsdoc + * @function AvatarManager.processKillAvatar + * @param {} message + * @param {} sendingNode + */ void processKillAvatar(QSharedPointer message, SharedNodePointer sendingNode); protected: diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h index 86a43c0c13..3c3858e2ba 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -39,7 +39,9 @@ class UserInputMapper; * methods. *
  • Use {@link Controller.parseMapping} or {@link Controller.loadMapping} to load a {@link Controller.MappingJSON}.
  • * - *

    Enable the mapping using {@link MappingObject#enable|enable} or {@link Controller.enableMapping} for it to take effect. + * + *

    Enable the mapping using {@link MappingObject#enable|enable} or {@link Controller.enableMapping} for it to take + * effect.

    * *

    Mappings and their routes are applied according to the following rules:

    *
      @@ -49,7 +51,7 @@ class UserInputMapper; * output that already has a route the new route is ignored. *
    • New mappings override previous mappings: each output is processed using the route in the most recently enabled * mapping that contains that output.
    • - *

      + *
    * * @class MappingObject */ diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 0336638068..d33f3e3383 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -29,7 +29,8 @@ class ScriptingInterface; *

    A route in a {@link MappingObject} used by the {@link Controller} API.

    * *

    Create a route using {@link MappingObject} methods and apply this object's methods to process it, terminating with - * {@link RouteObject#to} to apply it to a Standard control, action, or script function.

    + * {@link RouteObject#to} to apply it to a Standard control, action, or script function. Note: Loops are not + * permitted.

    * *

    Some methods apply to routes with number data, some apply routes with {@link Pose} data, and some apply to both route * types.

    diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index de115b0554..bc6ed63363 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -172,6 +172,17 @@ private: ReticleInterface* _reticleInterface { nullptr }; }; +/**jsdoc + * @namespace Reticle + * @property {boolean} allowMouseCapture + * @property {number} depth + * @property {Vec2} maximumPosition + * @property {boolean} mouseCaptured + * @property {boolean} pointingAtSystemOverlay + * @property {Vec2} position + * @property {number} scale + * @property {boolean} visible + */ // Scripting interface available to control the Reticle class ReticleInterface : public QObject { Q_OBJECT @@ -187,25 +198,82 @@ class ReticleInterface : public QObject { public: ReticleInterface(CompositorHelper* outer) : QObject(outer), _compositor(outer) {} + /**jsdoc + * @function Reticle.isMouseCaptured + * @returns {boolean} + */ Q_INVOKABLE bool isMouseCaptured() { return _compositor->shouldCaptureMouse(); } + /**jsdoc + * @function Reticle.getAllowMouseCapture + * @returns {boolean} + */ Q_INVOKABLE bool getAllowMouseCapture() { return _compositor->getAllowMouseCapture(); } + + /**jsdoc + * @function Reticle.setAllowMouseCapture + * @param {boolean} allowMouseCaptured + */ Q_INVOKABLE void setAllowMouseCapture(bool value) { return _compositor->setAllowMouseCapture(value); } + /**jsdoc + * @function Reticle.isPointingAtSystemOverlay + * @returns {boolean} + */ Q_INVOKABLE bool isPointingAtSystemOverlay() { return !_compositor->getReticleOverDesktop(); } + /**jsdoc + * @function Reticle.getVisible + * @returns {boolean} + */ Q_INVOKABLE bool getVisible() { return _compositor->getReticleVisible(); } + + /**jsdoc + * @function Reticle.setVisible + * @param {boolean} visible + */ Q_INVOKABLE void setVisible(bool visible) { _compositor->setReticleVisible(visible); } + /**jsdoc + * @function Reticle.getDepth + * @returns {number} + */ Q_INVOKABLE float getDepth() { return _compositor->getReticleDepth(); } + + /**jsdoc + * @function Reticle.setDepth + * @param {number} depth + */ Q_INVOKABLE void setDepth(float depth) { _compositor->setReticleDepth(depth); } + /**jsdoc + * @function Reticle.getScale + * @returns {number} + */ Q_INVOKABLE float getScale() const; + + /**jsdoc + * @function Reticle.setScale + * @param {number} scale + */ Q_INVOKABLE void setScale(float scale); + /**jsdoc + * @function Reticle.getPosition + * @returns {Vec2} + */ Q_INVOKABLE QVariant getPosition() const; + + /**jsdoc + * @function Reticle.setPosition + * @param {Vec2} position + */ Q_INVOKABLE void setPosition(QVariant position); + /**jsdoc + * @function Reticle.getMaximumPosition + * @returns {Vec2} + */ Q_INVOKABLE glm::vec2 getMaximumPosition() { return _compositor->getReticleMaximumPosition(); } private: diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f647082d73..693e3d0cf4 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -25,7 +25,7 @@ #include #include "EntitiesRendererLogging.h" - +#include using namespace render; using namespace render::entities; @@ -45,6 +45,7 @@ static int DEFAULT_MAX_FPS = 10; static int YOUTUBE_MAX_FPS = 30; static QTouchDevice _touchDevice; +static const char* URL_PROPERTY = "url"; WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) { if (urlString.isEmpty()) { @@ -52,7 +53,7 @@ WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& } const QUrl url(urlString); - if (url.scheme() == "http" || url.scheme() == "https" || + if (url.scheme() == URL_SCHEME_HTTP || url.scheme() == URL_SCHEME_HTTPS || urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { return ContentType::HtmlContent; } @@ -164,6 +165,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene if (urlChanged) { if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { destroyWebSurface(); + // If we destroyed the surface, the URL change will be implicitly handled by the re-creation + urlChanged = false; } withWriteLock([&] { @@ -185,8 +188,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene return; } - if (urlChanged) { - _webSurface->getRootItem()->setProperty("url", _lastSourceUrl); + if (urlChanged && _contentType == ContentType::HtmlContent) { + _webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl); } if (_contextPosition != entity->getWorldPosition()) { @@ -246,14 +249,25 @@ void WebEntityRenderer::doRender(RenderArgs* args) { batch.setResourceTexture(0, _texture); float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; + // Turn off jitter for these entities + batch.pushProjectionJitter(); DependencyManager::get()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId); + batch.popProjectionJitter(); } bool WebEntityRenderer::hasWebSurface() { return (bool)_webSurface && _webSurface->getRootItem(); } +static const auto WebSurfaceDeleter = [](OffscreenQmlSurface* webSurface) { + AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete webSurface; + }); +}; + bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; @@ -261,20 +275,9 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { } ++_currentWebCount; - auto deleter = [](OffscreenQmlSurface* webSurface) { - AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] { - if (AbstractViewStateInterface::instance()->isAboutToQuit()) { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete webSurface; - } else { - webSurface->deleteLater(); - } - }); - }; // FIXME use the surface cache instead of explicit creation - _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); + _webSurface = QSharedPointer(new OffscreenQmlSurface(), WebSurfaceDeleter); // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) _webSurface->setMaxFps(DEFAULT_MAX_FPS); @@ -302,15 +305,10 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { _webSurface->setMaxFps(DEFAULT_MAX_FPS); } _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty("url", _lastSourceUrl); + item->setProperty(URL_PROPERTY, _lastSourceUrl); }); } else if (_contentType == ContentType::QmlContent) { - _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { - if (item && item->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - } - }); + _webSurface->load(_lastSourceUrl); } _fadeStartTime = usecTimestampNow(); _webSurface->resume(); @@ -320,27 +318,21 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { void WebEntityRenderer::destroyWebSurface() { QSharedPointer webSurface; + ContentType contentType{ ContentType::NoContent }; withWriteLock([&] { webSurface.swap(_webSurface); + std::swap(contentType, _contentType); }); if (webSurface) { --_currentWebCount; QQuickItem* rootItem = webSurface->getRootItem(); - if (rootItem && rootItem->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); - } - // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem) { - QObject* obj = rootItem->findChild("webEngineView"); - if (obj) { - // stop loading - QMetaObject::invokeMethod(obj, "stop"); - } + if (rootItem && contentType == ContentType::HtmlContent) { + // stop loading + QMetaObject::invokeMethod(rootItem, "stop"); } webSurface->pause(); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index c5824abef0..2f8fd47b79 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -69,7 +69,6 @@ private: graphics::SkyboxPointer editSkybox() { return editBackground()->getSkybox(); } graphics::HazePointer editHaze() { _needHazeUpdate = true; return _haze; } - bool _needsInitialSimulation{ true }; glm::vec3 _lastPosition; glm::vec3 _lastDimensions; glm::quat _lastRotation; diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index 82af60ed1a..43c6b7a6a5 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -216,7 +216,6 @@ bool AnimationPropertyGroup::appendToEditPacket(OctreePacketData* packetData, bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes) { - int bytesRead = 0; bool overwriteLocalData = true; bool somethingChanged = false; @@ -360,3 +359,21 @@ int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, setHold); return bytesRead; } + +float AnimationPropertyGroup::getNumFrames() const { + return _lastFrame - _firstFrame + 1.0f; +} + +float AnimationPropertyGroup::computeLoopedFrame(float frame) const { + float numFrames = getNumFrames(); + if (numFrames > 1.0f) { + frame = getFirstFrame() + fmodf(frame - getFirstFrame(), numFrames); + } else { + frame = getFirstFrame(); + } + return frame; +} + +bool AnimationPropertyGroup::isValidAndRunning() const { + return getRunning() && (getFPS() > 0.0f) && (getNumFrames() > 1.0f) && !(getURL().isEmpty()); +} diff --git a/libraries/entities/src/AnimationPropertyGroup.h b/libraries/entities/src/AnimationPropertyGroup.h index 54d4ced92f..bebfe2c194 100644 --- a/libraries/entities/src/AnimationPropertyGroup.h +++ b/libraries/entities/src/AnimationPropertyGroup.h @@ -77,6 +77,10 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; + float getNumFrames() const; + float computeLoopedFrame(float frame) const; + bool isValidAndRunning() const; + DEFINE_PROPERTY_REF(PROP_ANIMATION_URL, URL, url, QString, ""); DEFINE_PROPERTY(PROP_ANIMATION_FPS, FPS, fps, float, 30.0f); DEFINE_PROPERTY(PROP_ANIMATION_FRAME_INDEX, CurrentFrame, currentFrame, float, 0.0f); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 2e3a6c9552..1d81a6ae6d 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -942,11 +942,10 @@ void EntityItem::setMass(float mass) { float volume = _volumeMultiplier * dimensions.x * dimensions.y * dimensions.z; // compute new density - const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3 float newDensity = 1.0f; - if (volume < 1.0e-6f) { + if (volume < ENTITY_ITEM_MIN_VOLUME) { // avoid divide by zero - newDensity = glm::min(mass / MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY); + newDensity = glm::min(mass / ENTITY_ITEM_MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY); } else { newDensity = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); } @@ -1688,7 +1687,7 @@ void EntityItem::setScaledDimensions(const glm::vec3& value) { } void EntityItem::setUnscaledDimensions(const glm::vec3& value) { - glm::vec3 newDimensions = glm::max(value, glm::vec3(0.0f)); // can never have negative dimensions + glm::vec3 newDimensions = glm::max(value, glm::vec3(ENTITY_ITEM_MIN_DIMENSION)); if (getUnscaledDimensions() != newDimensions) { withWriteLock([&] { _unscaledDimensions = newDimensions; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 0557bbe5ad..a88250a133 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -597,7 +597,7 @@ protected: // // DirtyFlags are set whenever a property changes that the EntitySimulation needs to know about. - uint32_t _flags { 0 }; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation + std::atomic_uint _flags { 0 }; // things that have changed from EXTERNAL changes (via script or packet) but NOT from simulation // these backpointers are only ever set/cleared by friends: EntityTreeElementPointer _element; // set by EntityTreeElement diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 0e0c2994cd..d2ddd687dd 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -60,8 +60,10 @@ const float ENTITY_ITEM_DEFAULT_LIFETIME = ENTITY_ITEM_IMMORTAL_LIFETIME; const glm::vec3 ENTITY_ITEM_DEFAULT_POSITION = ENTITY_ITEM_ZERO_VEC3; const glm::quat ENTITY_ITEM_DEFAULT_ROTATION; const float ENTITY_ITEM_DEFAULT_WIDTH = 0.1f; +const float ENTITY_ITEM_MIN_DIMENSION = 0.001f; const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(ENTITY_ITEM_DEFAULT_WIDTH); const float ENTITY_ITEM_DEFAULT_VOLUME = ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH; +const float ENTITY_ITEM_MIN_VOLUME = ENTITY_ITEM_MIN_DIMENSION * ENTITY_ITEM_MIN_DIMENSION * ENTITY_ITEM_MIN_DIMENSION; const float ENTITY_ITEM_MAX_DENSITY = 10000.0f; // kg/m^3 density of silver const float ENTITY_ITEM_MIN_DENSITY = 100.0f; // kg/m^3 density of balsa wood diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 0eec5cf04b..7c16214a78 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -766,6 +766,36 @@ QVector EntityScriptingInterface::findEntitiesByType(const QString entity return result; } +QVector EntityScriptingInterface::findEntitiesByName(const QString entityName, const glm::vec3& center, float radius, bool caseSensitiveSearch) const { + + QVector result; + if (_entityTree) { + QVector entities; + _entityTree->withReadLock([&] { + _entityTree->findEntities(center, radius, entities); + }); + + if (caseSensitiveSearch) { + foreach(EntityItemPointer entity, entities) { + if (entity->getName() == entityName) { + result << entity->getEntityItemID(); + } + } + + } else { + QString entityNameLowerCase = entityName.toLower(); + + foreach(EntityItemPointer entity, entities) { + QString entityItemLowerCase = entity->getName().toLower(); + if (entityItemLowerCase == entityNameLowerCase) { + result << entity->getEntityItemID(); + } + } + } + } + return result; +} + RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { QVector entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 73422efefa..5e76d18515 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -387,6 +387,22 @@ public slots: /// this function will not find any entities in script engine contexts which don't have access to entities Q_INVOKABLE QVector findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const; + /**jsdoc + * Find all entities of a particular name that intersect a sphere defined by a center point and radius. + * @function Entities.findEntitiesByName + * @param {string} entityName - The name of the entity to search for. + * @param {Vec3} center - The point about which to search. + * @param {number} radius - The radius within which to search. + * @param {boolean} [caseSensitive=false] - If true then the search is case-sensitive. + * @returns {Uuid[]} An array of entity IDs that have the specified name and intersect the search sphere. The array is empty + * if no entities could be found. + * @example Report the number of entities with the name, "Light-Target". + * var entityIDs = Entities.findEntitiesByName("Light-Target", MyAvatar.position, 10, false); + * print("Number of entities with the name "Light-Target": " + entityIDs.length); + */ + Q_INVOKABLE QVector findEntitiesByName(const QString entityName, const glm::vec3& center, float radius, + bool caseSensitiveSearch = false ) const; + /**jsdoc * Find the first entity intersected by a {@link PickRay}. Light and Zone entities are not * intersected unless they've been configured as pickable using {@link Entities.setLightsArePickable|setLightsArePickable} diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index d391dc8be1..b56f367e0a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1172,16 +1172,6 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) _challengeOwnershipTimeoutTimer->start(5000); } -void EntityTree::startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { - qCDebug(entities) << "'transfer_status' is 'pending', checking again in 90 seconds..." << entityItemID; - QTimer* transferStatusRetryTimer = new QTimer(this); - connect(transferStatusRetryTimer, &QTimer::timeout, this, [=]() { - validatePop(certID, entityItemID, senderNode, true); - }); - transferStatusRetryTimer->setSingleShot(true); - transferStatusRetryTimer->start(90000); -} - QByteArray EntityTree::computeNonce(const QString& certID, const QString ownerKey) { QUuid nonce = QUuid::createUuid(); //random, 5-hex value, separated by "-" QByteArray nonceBytes = nonce.toByteArray(); @@ -1321,7 +1311,7 @@ void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, c nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(QUuid::fromRfc4122(nodeToChallenge)))); } -void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) { +void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { // Start owner verification. auto nodeList = DependencyManager::get(); // First, asynchronously hit "proof_of_purchase_status?transaction_type=transfer" endpoint. @@ -1352,30 +1342,13 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt withWriteLock([&] { deleteEntity(entityItemID, true); }); - } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { - if (isRetryingValidation) { - qCDebug(entities) << "'transfer_status' is 'pending' after retry, deleting entity" << entityItemID; - withWriteLock([&] { - deleteEntity(entityItemID, true); - }); - } else { - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startPendingTransferStatusTimer", - Q_ARG(const QString&, certID), - Q_ARG(const EntityItemID&, entityItemID), - Q_ARG(const SharedNodePointer&, senderNode)); - return; - } else { - startPendingTransferStatusTimer(certID, entityItemID, senderNode); - } - } } else { // Second, challenge ownership of the PoP cert + // (ignore pending status; a failure will be cleaned up during DDV) sendChallengeOwnershipPacket(certID, jsonObject["transfer_recipient_key"].toString(), entityItemID, senderNode); - } } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID @@ -1619,7 +1592,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c // Delete the entity we just added if it doesn't pass static certificate verification deleteEntity(entityItemID, true); } else { - validatePop(properties.getCertificateID(), entityItemID, senderNode, false); + validatePop(properties.getCertificateID(), entityItemID, senderNode); } } diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index a080801a0e..3289101967 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -397,12 +397,11 @@ protected: QHash _entitiesToAdd; Q_INVOKABLE void startChallengeOwnershipTimer(const EntityItemID& entityItemID); - Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); private: void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& text, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); - void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); + void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); std::shared_ptr _myAvatar{ nullptr }; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index be62664ff9..0f59bc673d 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -82,12 +82,12 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslations, setJointTranslations); SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints); - bool somethingChangedInAnimations = _animationProperties.setProperties(properties); - - if (somethingChangedInAnimations) { - _flags |= Simulation::DIRTY_UPDATEABLE; - } - somethingChanged = somethingChanged || somethingChangedInAnimations; + withWriteLock([&] { + AnimationPropertyGroup animationProperties = _animationProperties; + animationProperties.setProperties(properties); + bool somethingChangedInAnimations = applyNewAnimationProperties(animationProperties); + somethingChanged = somethingChanged || somethingChangedInAnimations; + }); if (somethingChanged) { bool wantDebug = false; @@ -118,12 +118,16 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); + // grab a local copy of _animationProperties to avoid multiple locks int bytesFromAnimation; - withWriteLock([&] { - // Note: since we've associated our _animationProperties with our _animationLoop, the readEntitySubclassDataFromBuffer() - // will automatically read into the animation loop - bytesFromAnimation = _animationProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + withReadLock([&] { + AnimationPropertyGroup animationProperties = _animationProperties; + bytesFromAnimation = animationProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, animationPropertiesChanged); + if (animationPropertiesChanged) { + applyNewAnimationProperties(animationProperties); + somethingChanged = true; + } }); bytesRead += bytesFromAnimation; @@ -131,11 +135,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); - if (animationPropertiesChanged) { - _flags |= Simulation::DIRTY_UPDATEABLE; - somethingChanged = true; - } - READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, QVector, setJointRotationsSet); READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, QVector, setJointRotations); READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, QVector, setJointTranslationsSet); @@ -194,98 +193,38 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit // added update function back for property fix void ModelEntityItem::update(const quint64& now) { + assert(_lastAnimated > 0); - { - auto currentAnimationProperties = this->getAnimationProperties(); - - if (_previousAnimationProperties != currentAnimationProperties) { - withWriteLock([&] { - // if we hit start animation or change the first or last frame then restart the animation - if ((currentAnimationProperties.getFirstFrame() != _previousAnimationProperties.getFirstFrame()) || - (currentAnimationProperties.getLastFrame() != _previousAnimationProperties.getLastFrame()) || - (currentAnimationProperties.getRunning() && !_previousAnimationProperties.getRunning())) { - - // when we start interface and the property is are set then the current frame is initialized to -1 - if (_currentFrame < 0) { - // don't reset _lastAnimated here because we need the timestamp from the ModelEntityItem constructor for when the properties were set - _currentFrame = currentAnimationProperties.getCurrentFrame(); - setAnimationCurrentFrame(_currentFrame); - } else { - _lastAnimated = usecTimestampNow(); - _currentFrame = currentAnimationProperties.getFirstFrame(); - setAnimationCurrentFrame(currentAnimationProperties.getFirstFrame()); - } - } else if (!currentAnimationProperties.getRunning() && _previousAnimationProperties.getRunning()) { - _currentFrame = currentAnimationProperties.getFirstFrame(); - setAnimationCurrentFrame(_currentFrame); - } else if (currentAnimationProperties.getCurrentFrame() != _previousAnimationProperties.getCurrentFrame()) { - // don't reset _lastAnimated here because the currentFrame was set with the previous setting of _lastAnimated - _currentFrame = currentAnimationProperties.getCurrentFrame(); - } - - }); - _previousAnimationProperties = this->getAnimationProperties(); - - } - - if (isAnimatingSomething()) { - if (!(getAnimationFirstFrame() < 0) && !(getAnimationFirstFrame() > getAnimationLastFrame())) { - updateFrameCount(); - } - } - } - - EntityItem::update(now); -} - -bool ModelEntityItem::needsToCallUpdate() const { - - return true; -} - -void ModelEntityItem::updateFrameCount() { - - if (_currentFrame < 0.0f) { - return; - } - - if (!_lastAnimated) { - _lastAnimated = usecTimestampNow(); - return; - } - - auto now = usecTimestampNow(); - - // update the interval since the last animation. + // increment timestamp before checking "hold" auto interval = now - _lastAnimated; _lastAnimated = now; - // if fps is negative then increment timestamp and return. - if (getAnimationFPS() < 0.0f) { + // grab a local copy of _animationProperties to avoid multiple locks + auto animationProperties = getAnimationProperties(); + + // bail on "hold" + if (animationProperties.getHold()) { return; } - int updatedFrameCount = getAnimationLastFrame() - getAnimationFirstFrame() + 1; - - if (!getAnimationHold() && getAnimationIsPlaying()) { - float deltaTime = (float)interval / (float)USECS_PER_SECOND; - _currentFrame += (deltaTime * getAnimationFPS()); - if (_currentFrame > getAnimationLastFrame() + 1) { - if (getAnimationLoop() && getAnimationFirstFrame() != getAnimationLastFrame()) { - _currentFrame = getAnimationFirstFrame() + (int)(_currentFrame - getAnimationFirstFrame()) % updatedFrameCount; - } else { - _currentFrame = getAnimationLastFrame(); - } - } else if (_currentFrame < getAnimationFirstFrame()) { - if (getAnimationFirstFrame() < 0) { - _currentFrame = 0; - } else { - _currentFrame = getAnimationFirstFrame(); - } + // increment animation frame + _currentFrame += (animationProperties.getFPS() * ((float)interval) / (float)USECS_PER_SECOND); + if (_currentFrame > animationProperties.getLastFrame() + 1.0f) { + if (animationProperties.getLoop()) { + _currentFrame = animationProperties.computeLoopedFrame(_currentFrame); + } else { + _currentFrame = animationProperties.getLastFrame(); + } + } else if (_currentFrame < animationProperties.getFirstFrame()) { + if (animationProperties.getFirstFrame() < 0.0f) { + _currentFrame = 0.0f; + } else { + _currentFrame = animationProperties.getFirstFrame(); } - // qCDebug(entities) << "in update frame " << _currentFrame; - setAnimationCurrentFrame(_currentFrame); } + setAnimationCurrentFrame(_currentFrame); + + EntityItem::update(now); } void ModelEntityItem::debugDump() const { @@ -361,67 +300,61 @@ void ModelEntityItem::setAnimationURL(const QString& url) { } void ModelEntityItem::setAnimationSettings(const QString& value) { - // the animations setting is a JSON string that may contain various animation settings. - // if it includes fps, currentFrame, or running, those values will be parsed out and - // will over ride the regular animation settings + // NOTE: this method only called for old bitstream format - QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); - QJsonObject settingsAsJsonObject = settingsAsJson.object(); - QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); - if (settingsMap.contains("fps")) { - float fps = settingsMap["fps"].toFloat(); - setAnimationFPS(fps); - } + withWriteLock([&] { + auto animationProperties = _animationProperties; - // old settings used frameIndex - if (settingsMap.contains("frameIndex")) { - float currentFrame = settingsMap["frameIndex"].toFloat(); -#ifdef WANT_DEBUG - if (!getAnimationURL().isEmpty()) { - qCDebug(entities) << "ModelEntityItem::setAnimationSettings() calling setAnimationFrameIndex()..."; - qCDebug(entities) << " model URL:" << getModelURL(); - qCDebug(entities) << " animation URL:" << getAnimationURL(); - qCDebug(entities) << " settings:" << value; - qCDebug(entities) << " settingsMap[frameIndex]:" << settingsMap["frameIndex"]; - qCDebug(entities" currentFrame: %20.5f", currentFrame); + // the animations setting is a JSON string that may contain various animation settings. + // if it includes fps, currentFrame, or running, those values will be parsed out and + // will over ride the regular animation settings + QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8()); + QJsonObject settingsAsJsonObject = settingsAsJson.object(); + QVariantMap settingsMap = settingsAsJsonObject.toVariantMap(); + if (settingsMap.contains("fps")) { + float fps = settingsMap["fps"].toFloat(); + animationProperties.setFPS(fps); } -#endif - setAnimationCurrentFrame(currentFrame); - } - - if (settingsMap.contains("running")) { - bool running = settingsMap["running"].toBool(); - if (running != getAnimationIsPlaying()) { - setAnimationIsPlaying(running); + // old settings used frameIndex + if (settingsMap.contains("frameIndex")) { + float currentFrame = settingsMap["frameIndex"].toFloat(); + animationProperties.setCurrentFrame(currentFrame); } - } - if (settingsMap.contains("firstFrame")) { - float firstFrame = settingsMap["firstFrame"].toFloat(); - setAnimationFirstFrame(firstFrame); - } + if (settingsMap.contains("running")) { + bool running = settingsMap["running"].toBool(); + if (running != animationProperties.getRunning()) { + animationProperties.setRunning(running); + } + } - if (settingsMap.contains("lastFrame")) { - float lastFrame = settingsMap["lastFrame"].toFloat(); - setAnimationLastFrame(lastFrame); - } + if (settingsMap.contains("firstFrame")) { + float firstFrame = settingsMap["firstFrame"].toFloat(); + animationProperties.setFirstFrame(firstFrame); + } - if (settingsMap.contains("loop")) { - bool loop = settingsMap["loop"].toBool(); - setAnimationLoop(loop); - } + if (settingsMap.contains("lastFrame")) { + float lastFrame = settingsMap["lastFrame"].toFloat(); + animationProperties.setLastFrame(lastFrame); + } - if (settingsMap.contains("hold")) { - bool hold = settingsMap["hold"].toBool(); - setAnimationHold(hold); - } + if (settingsMap.contains("loop")) { + bool loop = settingsMap["loop"].toBool(); + animationProperties.setLoop(loop); + } - if (settingsMap.contains("allowTranslation")) { - bool allowTranslation = settingsMap["allowTranslation"].toBool(); - setAnimationAllowTranslation(allowTranslation); - } - _flags |= Simulation::DIRTY_UPDATEABLE; + if (settingsMap.contains("hold")) { + bool hold = settingsMap["hold"].toBool(); + animationProperties.setHold(hold); + } + + if (settingsMap.contains("allowTranslation")) { + bool allowTranslation = settingsMap["allowTranslation"].toBool(); + animationProperties.setAllowTranslation(allowTranslation); + } + applyNewAnimationProperties(animationProperties); + }); } void ModelEntityItem::setAnimationIsPlaying(bool value) { @@ -713,11 +646,45 @@ float ModelEntityItem::getAnimationFPS() const { }); } - bool ModelEntityItem::isAnimatingSomething() const { return resultWithReadLock([&] { - return !_animationProperties.getURL().isEmpty() && - _animationProperties.getRunning() && - (_animationProperties.getFPS() != 0.0f); - }); + return _animationProperties.isValidAndRunning(); + }); +} + +bool ModelEntityItem::applyNewAnimationProperties(AnimationPropertyGroup newProperties) { + // call applyNewAnimationProperties() whenever trying to update _animationProperties + // because there is some reset logic we need to do whenever the animation "config" properties change + // NOTE: this private method is always called inside withWriteLock() + + // if we hit start animation or change the first or last frame then restart the animation + if ((newProperties.getFirstFrame() != _animationProperties.getFirstFrame()) || + (newProperties.getLastFrame() != _animationProperties.getLastFrame()) || + (newProperties.getRunning() && !_animationProperties.getRunning())) { + + // when we start interface and the property is are set then the current frame is initialized to -1 + if (_currentFrame < 0.0f) { + // don't reset _lastAnimated here because we need the timestamp from the ModelEntityItem constructor for when the properties were set + _currentFrame = newProperties.getCurrentFrame(); + newProperties.setCurrentFrame(_currentFrame); + } else { + _lastAnimated = usecTimestampNow(); + _currentFrame = newProperties.getFirstFrame(); + newProperties.setCurrentFrame(newProperties.getFirstFrame()); + } + } else if (!newProperties.getRunning() && _animationProperties.getRunning()) { + _currentFrame = newProperties.getFirstFrame(); + newProperties.setCurrentFrame(_currentFrame); + } else if (newProperties.getCurrentFrame() != _animationProperties.getCurrentFrame()) { + // don't reset _lastAnimated here because the currentFrame was set with the previous setting of _lastAnimated + _currentFrame = newProperties.getCurrentFrame(); + } + + // finally apply the changes + bool somethingChanged = newProperties != _animationProperties; + if (somethingChanged) { + _animationProperties = newProperties; + _flags |= Simulation::DIRTY_UPDATEABLE; + } + return somethingChanged; } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 327606ae2f..ad6cdf4040 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -46,10 +46,9 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - // update() and needstocallupdate() added back for the entity property fix + virtual void update(const quint64& now) override; - virtual bool needsToCallUpdate() const override; - void updateFrameCount(); + bool needsToCallUpdate() const override { return isAnimatingSomething(); } virtual void debugDump() const override; @@ -132,6 +131,7 @@ public: private: void setAnimationSettings(const QString& value); // only called for old bitstream format + bool applyNewAnimationProperties(AnimationPropertyGroup newProperties); ShapeType computeTrueShapeType() const; protected: @@ -172,7 +172,6 @@ protected: private: uint64_t _lastAnimated{ 0 }; - AnimationPropertyGroup _previousAnimationProperties; float _currentFrame{ -1.0f }; }; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 871c451c50..c76419af02 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -77,8 +77,6 @@ class PolyLineEntityItem : public EntityItem { QString getTextures() const; void setTextures(const QString& textures); - virtual bool needsToCallUpdate() const override { return true; } - virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; } bool pointsChanged() const { return _pointsChanged; } diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 301011928b..cf42e93821 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -38,11 +38,8 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { entity->clearSimulationOwnership(); entity->markAsChangedOnServer(); if (auto element = entity->getElement()) { - auto tree = getEntityTree(); - tree->withReadLock([&] { - DirtyOctreeElementOperator op(element); - tree->recurseTreeWithOperator(&op); - }); + DirtyOctreeElementOperator op(element); + getEntityTree()->recurseTreeWithOperator(&op); } } else { ++itemItr; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 1e59646795..86422ef70c 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -996,14 +996,12 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS QByteArray filename = subobject.properties.at(0).toByteArray(); QByteArray filepath = filename.replace('\\', '/'); filename = fileOnUrl(filepath, url); - qDebug() << "Filename" << filepath << filename; _textureFilepaths.insert(getID(object.properties), filepath); _textureFilenames.insert(getID(object.properties), filename); } else if (subobject.name == "TextureName" && subobject.properties.length() >= TEXTURE_NAME_MIN_SIZE) { // trim the name from the timestamp QString name = QString(subobject.properties.at(0).toByteArray()); name = name.left(name.indexOf('[')); - qDebug() << "Filename" << name; _textureNames.insert(getID(object.properties), name); } else if (subobject.name == "Texture_Alpha_Source" && subobject.properties.length() >= TEXTURE_ALPHA_SOURCE_MIN_SIZE) { tex.assign(tex.alphaSource, subobject.properties.at(0).value()); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp index 9bb02d678d..501e59f38e 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.cpp @@ -44,8 +44,9 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::gl::GLBackend::do_setModelTransform), (&::gpu::gl::GLBackend::do_setViewTransform), - (&::gpu::gl::GLBackend::do_setProjectionTransform), - (&::gpu::gl::GLBackend::do_setViewportTransform), + (&::gpu::gl::GLBackend::do_setProjectionTransform), + (&::gpu::gl::GLBackend::do_setProjectionJitter), + (&::gpu::gl::GLBackend::do_setViewportTransform), (&::gpu::gl::GLBackend::do_setDepthRangeTransform), (&::gpu::gl::GLBackend::do_setPipeline), @@ -166,7 +167,18 @@ void GLBackend::renderPassTransfer(const Batch& batch) { case Batch::COMMAND_drawIndexedInstanced: case Batch::COMMAND_multiDrawIndirect: case Batch::COMMAND_multiDrawIndexedIndirect: - _transform.preUpdate(_commandIndex, _stereo); + { + Vec2u outputSize{ 1,1 }; + + if (_output._framebuffer) { + outputSize.x = _output._framebuffer->getWidth(); + outputSize.y = _output._framebuffer->getHeight(); + } else if (glm::dot(_transform._projectionJitter, _transform._projectionJitter)>0.0f) { + qCWarning(gpugllogging) << "Jittering needs to have a frame buffer to be set"; + } + + _transform.preUpdate(_commandIndex, _stereo, outputSize); + } break; case Batch::COMMAND_disableContextStereo: @@ -179,8 +191,10 @@ void GLBackend::renderPassTransfer(const Batch& batch) { case Batch::COMMAND_setViewportTransform: case Batch::COMMAND_setViewTransform: - case Batch::COMMAND_setProjectionTransform: { - CommandCall call = _commandCalls[(*command)]; + case Batch::COMMAND_setProjectionTransform: + case Batch::COMMAND_setProjectionJitter: + { + CommandCall call = _commandCalls[(*command)]; (this->*(call))(batch, *offset); break; } @@ -254,6 +268,8 @@ void GLBackend::render(const Batch& batch) { if (!batch.isStereoEnabled()) { _stereo._enable = false; } + // Reset jitter + _transform._projectionJitter = Vec2(0.0f, 0.0f); { PROFILE_RANGE(render_gpu_gl_detail, "Transfer"); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 895a7777c9..5bbb44f9e1 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -126,6 +126,7 @@ public: virtual void do_setModelTransform(const Batch& batch, size_t paramOffset) final; virtual void do_setViewTransform(const Batch& batch, size_t paramOffset) final; virtual void do_setProjectionTransform(const Batch& batch, size_t paramOffset) final; + virtual void do_setProjectionJitter(const Batch& batch, size_t paramOffset) final; virtual void do_setViewportTransform(const Batch& batch, size_t paramOffset) final; virtual void do_setDepthRangeTransform(const Batch& batch, size_t paramOffset) final; @@ -367,6 +368,7 @@ protected: Mat4 _projection; Vec4i _viewport { 0, 0, 1, 1 }; Vec2 _depthRange { 0.0f, 1.0f }; + Vec2 _projectionJitter{ 0.0f, 0.0f }; bool _invalidView { false }; bool _invalidProj { false }; bool _invalidViewport { false }; @@ -379,7 +381,7 @@ protected: mutable List::const_iterator _camerasItr; mutable size_t _currentCameraOffset{ INVALID_OFFSET }; - void preUpdate(size_t commandIndex, const StereoState& stereo); + void preUpdate(size_t commandIndex, const StereoState& stereo, Vec2u framebufferSize); void update(size_t commandIndex, const StereoState& stereo) const; void bindCurrentCamera(int stereoSide) const; } _transform; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp index 72aaa5aa66..35d292cd46 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp @@ -28,6 +28,12 @@ void GLBackend::do_setProjectionTransform(const Batch& batch, size_t paramOffset _transform._invalidProj = true; } +void GLBackend::do_setProjectionJitter(const Batch& batch, size_t paramOffset) { + _transform._projectionJitter.x = batch._params[paramOffset]._float; + _transform._projectionJitter.y = batch._params[paramOffset+1]._float; + _transform._invalidProj = true; +} + void GLBackend::do_setViewportTransform(const Batch& batch, size_t paramOffset) { memcpy(&_transform._viewport, batch.readData(batch._params[paramOffset]._uint), sizeof(Vec4i)); @@ -90,7 +96,7 @@ void GLBackend::syncTransformStateCache() { _transform._enabledDrawcallInfoBuffer = false; } -void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo) { +void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const StereoState& stereo, Vec2u framebufferSize) { // Check all the dirty flags and update the state accordingly if (_invalidViewport) { _camera._viewport = glm::vec4(_viewport); @@ -117,20 +123,21 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo if (_invalidView || _invalidProj || _invalidViewport) { size_t offset = _cameraUboSize * _cameras.size(); + Vec2 finalJitter = _projectionJitter / Vec2(framebufferSize); _cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset)); if (stereo.isStereo()) { #ifdef GPU_STEREO_CAMERA_BUFFER - _cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, _view), _camera.getEyeCamera(1, stereo, _view))); + _cameras.push_back(CameraBufferElement(_camera.getEyeCamera(0, stereo, _view, finalJitter), _camera.getEyeCamera(1, stereo, _view, finalJitter))); #else - _cameras.push_back((_camera.getEyeCamera(0, stereo, _view))); - _cameras.push_back((_camera.getEyeCamera(1, stereo, _view))); + _cameras.push_back((_camera.getEyeCamera(0, stereo, _view, finalJitter))); + _cameras.push_back((_camera.getEyeCamera(1, stereo, _view, finalJitter))); #endif } else { #ifdef GPU_STEREO_CAMERA_BUFFER - _cameras.push_back(CameraBufferElement(_camera.recomputeDerived(_view))); + _cameras.push_back(CameraBufferElement(_camera.getMonoCamera(_view, finalJitter))); #else - _cameras.push_back((_camera.recomputeDerived(_view))); + _cameras.push_back((_camera.getMonoCamera(_view, finalJitter))); #endif } } diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index db4941c163..84a7c275f0 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -265,6 +265,22 @@ void Batch::setProjectionTransform(const Mat4& proj) { _params.emplace_back(cacheData(sizeof(Mat4), &proj)); } +void Batch::setProjectionJitter(float jx, float jy) { + _projectionJitter.x = jx; + _projectionJitter.y = jy; + pushProjectionJitter(jx, jy); +} + +void Batch::pushProjectionJitter(float jx, float jy) { + ADD_COMMAND(setProjectionJitter); + _params.emplace_back(jx); + _params.emplace_back(jy); +} + +void Batch::popProjectionJitter() { + pushProjectionJitter(_projectionJitter.x, _projectionJitter.y); +} + void Batch::setViewportTransform(const Vec4i& viewport) { ADD_COMMAND(setViewportTransform); diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index 96b234295d..ed928e08e6 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -167,6 +167,10 @@ public: void resetViewTransform() { setViewTransform(Transform(), false); } void setViewTransform(const Transform& view, bool camera = true); void setProjectionTransform(const Mat4& proj); + void setProjectionJitter(float jx = 0.0f, float jy = 0.0f); + // Very simple 1 level stack management of jitter. + void pushProjectionJitter(float jx = 0.0f, float jy = 0.0f); + void popProjectionJitter(); // Viewport is xy = low left corner in framebuffer, zw = width height of the viewport, expressed in pixels void setViewportTransform(const Vec4i& viewport); void setDepthRangeTransform(float nearDepth, float farDepth); @@ -292,8 +296,9 @@ public: COMMAND_setModelTransform, COMMAND_setViewTransform, - COMMAND_setProjectionTransform, - COMMAND_setViewportTransform, + COMMAND_setProjectionTransform, + COMMAND_setProjectionJitter, + COMMAND_setViewportTransform, COMMAND_setDepthRangeTransform, COMMAND_setPipeline, @@ -496,6 +501,7 @@ public: NamedBatchDataMap _namedData; + glm::vec2 _projectionJitter{ 0.0f, 0.0f }; bool _enableStereo{ true }; bool _enableSkybox { false }; diff --git a/libraries/gpu/src/gpu/Color.slh b/libraries/gpu/src/gpu/Color.slh index d70e588f4d..384b8bd329 100644 --- a/libraries/gpu/src/gpu/Color.slh +++ b/libraries/gpu/src/gpu/Color.slh @@ -41,15 +41,19 @@ vec3 color_LinearToYCoCg(vec3 rgb) { ); } -vec3 color_YCoCgToLinear(vec3 ycocg) { +vec3 color_YCoCgToUnclampedLinear(vec3 ycocg) { // R = Y + Co - Cg // G = Y + Cg // B = Y - Co - Cg - return clamp(vec3( + return vec3( ycocg.x + ycocg.y - ycocg.z, ycocg.x + ycocg.z, ycocg.x - ycocg.y - ycocg.z - ), vec3(0.0), vec3(1.0)); + ); +} + +vec3 color_YCoCgToLinear(vec3 ycocg) { + return clamp(color_YCoCgToUnclampedLinear(ycocg), vec3(0.0), vec3(1.0)); } <@func declareColorWheel()@> diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 7dc6965076..75c80a0164 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -222,7 +222,7 @@ const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived(const return *this; } -Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const StereoState& _stereo, const Transform& xformView) const { +Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const StereoState& _stereo, const Transform& xformView, Vec2 normalizedJitter) const { TransformCamera result = *this; Transform offsetTransform = xformView; if (!_stereo._skybox) { @@ -231,6 +231,9 @@ Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const S // FIXME: If "skybox" the ipd is set to 0 for now, let s try to propose a better solution for this in the future } result._projection = _stereo._eyeProjections[eye]; + normalizedJitter.x *= 2.0f; + result._projection[2][0] += normalizedJitter.x; + result._projection[2][1] += normalizedJitter.y; result.recomputeDerived(offsetTransform); result._stereoInfo = Vec4(1.0f, (float)eye, 0.0f, 0.0f); @@ -238,6 +241,14 @@ Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const S return result; } +Backend::TransformCamera Backend::TransformCamera::getMonoCamera(const Transform& xformView, Vec2 normalizedJitter) const { + TransformCamera result = *this; + result._projection[2][0] += normalizedJitter.x; + result._projection[2][1] += normalizedJitter.y; + result.recomputeDerived(xformView); + return result; +} + // Counters for Buffer and Texture usage in GPU/Context ContextMetricSize Backend::freeGPUMemSize; diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 2df7de2331..eda8fee596 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -64,19 +64,16 @@ public: virtual void recycle() const = 0; virtual void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage) = 0; - // UBO class... layout MUST match the layout in Transform.slh - class TransformCamera { - public: - mutable Mat4 _view; - mutable Mat4 _viewInverse; - mutable Mat4 _projectionViewUntranslated; - Mat4 _projection; - mutable Mat4 _projectionInverse; - Vec4 _viewport; // Public value is int but float in the shader to stay in floats for all the transform computations. - mutable Vec4 _stereoInfo; + // Shared header between C++ and GLSL +#include "TransformCamera_shared.slh" + class TransformCamera : public _TransformCamera { + public: const Backend::TransformCamera& recomputeDerived(const Transform& xformView) const; - TransformCamera getEyeCamera(int eye, const StereoState& stereo, const Transform& xformView) const; + // Jitter should be divided by framebuffer size + TransformCamera getMonoCamera(const Transform& xformView, Vec2 normalizedJitter) const; + // Jitter should be divided by framebuffer size + TransformCamera getEyeCamera(int eye, const StereoState& stereo, const Transform& xformView, Vec2 normalizedJitter) const; }; @@ -136,7 +133,6 @@ protected: friend class Context; mutable ContextStats _stats; StereoState _stereo; - }; class Context { diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index b9b8544601..50c0bc13ed 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -11,20 +11,14 @@ <@def GPU_TRANSFORM_STATE_SLH@> <@func declareStandardCameraTransform()@> -struct TransformCamera { - mat4 _view; - mat4 _viewInverse; - mat4 _projectionViewUntranslated; - mat4 _projection; - mat4 _projectionInverse; - vec4 _viewport; - vec4 _stereoInfo; -}; +<@include gpu/TransformCamera_shared.slh@> + +#define TransformCamera _TransformCamera layout(std140) uniform transformCameraBuffer { #ifdef GPU_TRANSFORM_IS_STEREO #ifdef GPU_TRANSFORM_STEREO_CAMERA - TransformCamera _camera[2]; + TransformCamera _camera[2]; #else TransformCamera _camera; #endif diff --git a/libraries/gpu/src/gpu/TransformCamera_shared.slh b/libraries/gpu/src/gpu/TransformCamera_shared.slh new file mode 100644 index 0000000000..37521d8201 --- /dev/null +++ b/libraries/gpu/src/gpu/TransformCamera_shared.slh @@ -0,0 +1,26 @@ +// glsl / C++ compatible source as interface for FadeEffect +#ifdef __cplusplus +# define _MAT4 Mat4 +# define _VEC4 Vec4 +# define _MUTABLE mutable +#else +# define _MAT4 mat4 +# define _VEC4 vec4 +# define _MUTABLE +#endif + +struct _TransformCamera { + _MUTABLE _MAT4 _view; + _MUTABLE _MAT4 _viewInverse; + _MUTABLE _MAT4 _projectionViewUntranslated; + _MAT4 _projection; + _MUTABLE _MAT4 _projectionInverse; + _VEC4 _viewport; // Public value is int but float in the shader to stay in floats for all the transform computations. + _MUTABLE _VEC4 _stereoInfo; +}; + + // <@if 1@> + // Trigger Scribe include + // <@endif@> +// + diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 4dfa8b17ea..9532f39ce0 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -137,6 +137,62 @@ class ModelCache : public ResourceCache, public Dependency { SINGLETON_DEPENDENCY public: + + // Properties are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * API to manage model cache resources. + * @namespace ModelCache + * + * @property {number} numTotal - Total number of total resources. Read-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + */ + + + // Functions are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * Get the list of all resource URLs. + * @function ModelCache.getResourceList + * @return {string[]} + */ + + /**jsdoc + * @function ModelCache.dirty + * @returns {Signal} + */ + + /**jsdoc + * @function ModelCache.updateTotalSize + * @param {number} deltaSize + */ + + /**jsdoc + * @function ModelCache.prefetch + * @param {string} url + * @param {object} extra + * @returns {object} + */ + + /**jsdoc + * Asynchronously loads a resource from the specified URL and returns it. + * @function ModelCache.getResource + * @param {string} url - URL of the resource to load. + * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. + * @param {} [extra=null] + * @return {Resource} + */ + + /**jsdoc + * Prefetches a resource. + * @function ModelCache.prefetch + * @param {string} url - URL of the resource to prefetch. + * @return {Resource} + */ + + GeometryResource::Pointer getGeometryResource(const QUrl& url, const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl()); diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index b2740e2ca1..3f46dc3074 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -137,12 +137,69 @@ using NetworkTexturePointer = QSharedPointer; Q_DECLARE_METATYPE(QWeakPointer) + /// Stores cached textures, including render-to-texture targets. class TextureCache : public ResourceCache, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY public: + + // Properties are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * API to manage texture cache resources. + * @namespace TextureCache + * + * @property {number} numTotal - Total number of total resources. Read-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + */ + + + // Functions are copied over from ResourceCache (see ResourceCache.h for reason). + + /**jsdoc + * Get the list of all resource URLs. + * @function TextureCache.getResourceList + * @return {string[]} + */ + + /**jsdoc + * @function TextureCache.dirty + * @returns {Signal} + */ + + /**jsdoc + * @function TextureCache.updateTotalSize + * @param {number} deltaSize + */ + + /**jsdoc + * @function TextureCache.prefetch + * @param {string} url + * @param {object} extra + * @returns {object} + */ + + /**jsdoc + * Asynchronously loads a resource from the specified URL and returns it. + * @function TextureCache.getResource + * @param {string} url - URL of the resource to load. + * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. + * @param {} [extra=null] + * @return {Resource} + */ + + /**jsdoc + * Prefetches a resource. + * @function TextureCache.prefetch + * @param {string} url - URL of the resource to prefetch. + * @return {Resource} + */ + + /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture /// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and /// the second, a set of random unit vectors to be used as noise gradients. @@ -180,9 +237,20 @@ public: static const int DEFAULT_SPECTATOR_CAM_HEIGHT { 1024 }; signals: + /**jsdoc + * @function TextureCache.spectatorCameraFramebufferReset + * @returns {Signal} + */ void spectatorCameraFramebufferReset(); protected: + + /**jsdoc + * @function TextureCache.prefect + * @param {string} url + * @param {number} type + * @param {number} [maxNumPixels=67108864] + */ // Overload ResourceCache::prefetch to allow specifying texture type for loads Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 75fd0888a7..edb2992128 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -331,12 +331,14 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { return false; } +static const QString LOCALHOST = "localhost"; + bool isPossiblePlaceName(QString possiblePlaceName) { bool result { false }; int length = possiblePlaceName.length(); static const int MINIMUM_PLACENAME_LENGTH = 1; static const int MAXIMUM_PLACENAME_LENGTH = 64; - if (possiblePlaceName.toLower() != "localhost" && + if (possiblePlaceName.toLower() != LOCALHOST && length >= MINIMUM_PLACENAME_LENGTH && length <= MAXIMUM_PLACENAME_LENGTH) { const QRegExp PLACE_NAME_REGEX = QRegExp("^[0-9A-Za-z](([0-9A-Za-z]|-(?!-))*[^\\W_]$|$)"); result = PLACE_NAME_REGEX.indexIn(possiblePlaceName) == 0; @@ -356,7 +358,7 @@ void AddressManager::handleLookupString(const QString& lookupString, bool fromSu sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX); lookupURL = QUrl(sanitizedString); - if (lookupURL.scheme().isEmpty()) { + if (lookupURL.scheme().isEmpty() || lookupURL.scheme().toLower() == LOCALHOST) { lookupURL = QUrl("hifi://" + sanitizedString); } } else { @@ -605,7 +607,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri if (ipAddressRegex.indexIn(lookupString) != -1) { QString domainIPString = ipAddressRegex.cap(1); - quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; + quint16 domainPort = 0; if (!ipAddressRegex.cap(2).isEmpty()) { domainPort = (quint16) ipAddressRegex.cap(2).toInt(); } @@ -627,7 +629,7 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString, LookupTri if (hostnameRegex.indexIn(lookupString) != -1) { QString domainHostname = hostnameRegex.cap(1); - quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; + quint16 domainPort = 0; if (!hostnameRegex.cap(2).isEmpty()) { domainPort = (quint16)hostnameRegex.cap(2).toInt(); diff --git a/libraries/networking/src/BaseAssetScriptingInterface.h b/libraries/networking/src/BaseAssetScriptingInterface.h index 336f3f81db..497f627421 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.h +++ b/libraries/networking/src/BaseAssetScriptingInterface.h @@ -31,12 +31,54 @@ public: BaseAssetScriptingInterface(QObject* parent = nullptr); public slots: + + /**jsdoc + * @function Assets.isValidPath + * @param {string} input + * @returns {boolean} + */ bool isValidPath(QString input) { return AssetUtils::isValidPath(input); } + + /**jsdoc + * @function Assets.isValidFilePath + * @param {string} input + * @returns {boolean} + */ bool isValidFilePath(QString input) { return AssetUtils::isValidFilePath(input); } + + /**jsdoc + * @function Assets.getATPUrl + * @param {string} input + * @returns {string} + */ QUrl getATPUrl(QString input) { return AssetUtils::getATPUrl(input); } + + /**jsdoc + * @function Assets.extractAssetHash + * @param {string} input + * @returns {string} + */ QString extractAssetHash(QString input) { return AssetUtils::extractAssetHash(input); } + + /**jsdoc + * @function Assets.isValidHash + * @param {string} input + * @returns {boolean} + */ bool isValidHash(QString input) { return AssetUtils::isValidHash(input); } + + /**jsdoc + * @function Assets.hashData + * @param {} data + * @returns {object} + */ QByteArray hashData(const QByteArray& data) { return AssetUtils::hashData(data); } + + /**jsdoc + * @function Assets.hashDataHex + * @param {} data + * @returns {string} + */ QString hashDataHex(const QByteArray& data) { return hashData(data).toHex(); } protected: diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index cd8064c4c0..871dc26899 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -166,7 +166,12 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { } } - if (_domainURL != domainURL || _sockAddr.getPort() != domainURL.port()) { + auto domainPort = domainURL.port(); + if (domainPort == -1) { + domainPort = DEFAULT_DOMAIN_SERVER_PORT; + } + + if (_domainURL != domainURL || _sockAddr.getPort() != domainPort) { // re-set the domain info so that auth information is reloaded hardReset(); @@ -192,12 +197,10 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { emit domainURLChanged(_domainURL); - if (_sockAddr.getPort() != domainURL.port()) { - qCDebug(networking) << "Updated domain port to" << domainURL.port(); + if (_sockAddr.getPort() != domainPort) { + qCDebug(networking) << "Updated domain port to" << domainPort; + _sockAddr.setPort(domainPort); } - - // grab the port by reading the string after the colon - _sockAddr.setPort(domainURL.port()); } } diff --git a/libraries/networking/src/EntityScriptClient.cpp b/libraries/networking/src/EntityScriptClient.cpp index 75ae7369fb..1eab5bf2d7 100644 --- a/libraries/networking/src/EntityScriptClient.cpp +++ b/libraries/networking/src/EntityScriptClient.cpp @@ -192,8 +192,6 @@ void EntityScriptClient::handleNodeClientConnectionReset(SharedNodePointer node) return; } - //qCDebug(entity_script_client) << "EntityScriptClient detected client connection reset handshake with Asset Server - failing any pending requests"; - forceFailureOfPendingRequests(node); } diff --git a/libraries/networking/src/HMACAuth.cpp b/libraries/networking/src/HMACAuth.cpp deleted file mode 100644 index 42b5c48d93..0000000000 --- a/libraries/networking/src/HMACAuth.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// -// HMACAuth.cpp -// libraries/networking/src -// -// Created by Simon Walton on 3/19/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 -// - -#include -#include - -#include "HMACAuth.h" - -#include - -#if OPENSSL_VERSION_NUMBER >= 0x10100000 -HMACAuth::HMACAuth(AuthMethod authMethod) - : _hmacContext(HMAC_CTX_new()) - , _authMethod(authMethod) { } - -HMACAuth::~HMACAuth() -{ - HMAC_CTX_free(_hmacContext); -} - -#else - -HMACAuth::HMACAuth(AuthMethod authMethod) - : _hmacContext(new HMAC_CTX()) - , _authMethod(authMethod) { - HMAC_CTX_init(_hmacContext); -} - -HMACAuth::~HMACAuth() { - HMAC_CTX_cleanup(_hmacContext); - delete _hmacContext; -} -#endif - -bool HMACAuth::setKey(const char* keyValue, int keyLen) { - const EVP_MD* sslStruct = nullptr; - - switch (_authMethod) { - case MD5: - sslStruct = EVP_md5(); - break; - - case SHA1: - sslStruct = EVP_sha1(); - break; - - case SHA224: - sslStruct = EVP_sha224(); - break; - - case SHA256: - sslStruct = EVP_sha256(); - break; - - case RIPEMD160: - sslStruct = EVP_ripemd160(); - break; - - default: - return false; - } - - QMutexLocker lock(&_lock); - return (bool) HMAC_Init_ex(_hmacContext, keyValue, keyLen, sslStruct, nullptr); -} - -bool HMACAuth::setKey(const QUuid& uidKey) { - const QByteArray rfcBytes(uidKey.toRfc4122()); - return setKey(rfcBytes.constData(), rfcBytes.length()); -} - -bool HMACAuth::addData(const char* data, int dataLen) { - QMutexLocker lock(&_lock); - return (bool) HMAC_Update(_hmacContext, reinterpret_cast(data), dataLen); -} - -HMACAuth::HMACHash HMACAuth::result() { - HMACHash hashValue(EVP_MAX_MD_SIZE); - unsigned int hashLen; - QMutexLocker lock(&_lock); - HMAC_Final(_hmacContext, &hashValue[0], &hashLen); - hashValue.resize((size_t) hashLen); - // Clear state for possible reuse. - HMAC_Init_ex(_hmacContext, nullptr, 0, nullptr, nullptr); - return hashValue; -} diff --git a/libraries/networking/src/HMACAuth.h b/libraries/networking/src/HMACAuth.h deleted file mode 100644 index 0bf7a86ec1..0000000000 --- a/libraries/networking/src/HMACAuth.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// HMACAuth.h -// libraries/networking/src -// -// Created by Simon Walton on 3/19/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 -// - -#ifndef hifi_HMACAuth_h -#define hifi_HMACAuth_h - -#include -#include -#include - -class QUuid; - -class HMACAuth { -public: - enum AuthMethod { MD5, SHA1, SHA224, SHA256, RIPEMD160 }; - using HMACHash = std::vector; - - explicit HMACAuth(AuthMethod authMethod = MD5); - ~HMACAuth(); - - bool setKey(const char* keyValue, int keyLen); - bool setKey(const QUuid& uidKey); - bool addData(const char* data, int dataLen); - HMACHash result(); - -private: - QMutex _lock; - struct hmac_ctx_st* _hmacContext; - AuthMethod _authMethod; -}; - -#endif // hifi_HMACAuth_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index aaf1b58a0a..0660f02fd6 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -36,7 +36,6 @@ #include "HifiSockAddr.h" #include "NetworkLogging.h" #include "udt/Packet.h" -#include "HMACAuth.h" static Setting::Handle LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); @@ -303,6 +302,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe } } else { NLPacket::LocalID sourceLocalID = Node::NULL_LOCAL_ID; + // check if we were passed a sourceNode hint or if we need to look it up if (!sourceNode) { // figure out which node this is from @@ -315,6 +315,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe QUuid sourceID = sourceNode ? sourceNode->getUUID() : QUuid(); if (!sourceNode && + !isDomainServer() && sourceLocalID == getDomainLocalID() && packet.getSenderSockAddr() == getDomainSockAddr() && PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) { @@ -331,7 +332,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe if (verifiedPacket && !ignoreVerification) { QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); - QByteArray expectedHash = NLPacket::hashForPacketAndHMAC(packet, sourceNode->getAuthenticateHash()); + QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, sourceNode->getConnectionSecret()); // check if the md5 hash in the header matches the hash we would expect if (packetHeaderHash != expectedHash) { @@ -358,7 +359,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe } else { HIFI_FCDEBUG(networking(), - "Packet of type" << headerType << "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID)); + "Packet of type" << headerType << "received from unknown node with Local ID" << sourceLocalID); } } @@ -371,15 +372,15 @@ void LimitedNodeList::collectPacketStats(const NLPacket& packet) { _numCollectedBytes += packet.getDataSize(); } -void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth) { +void LimitedNodeList::fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret) { if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) { packet.writeSourceID(getSessionLocalID()); } - if (hmacAuth + if (!connectionSecret.isNull() && !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType()) && !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) { - packet.writeVerificationHash(*hmacAuth); + packet.writeVerificationHashGivenSecret(connectionSecret); } } @@ -395,17 +396,17 @@ qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node& emit dataSent(destinationNode.getType(), packet.getDataSize()); destinationNode.recordBytesSent(packet.getDataSize()); - return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), &destinationNode.getAuthenticateHash()); + return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getConnectionSecret()); } qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, - HMACAuth* hmacAuth) { + const QUuid& connectionSecret) { Q_ASSERT(!packet.isPartOfMessage()); Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket", "Trying to send a reliable packet unreliably."); collectPacketStats(packet); - fillPacketHeader(packet, hmacAuth); + fillPacketHeader(packet, connectionSecret); return _nodeSocket.writePacket(packet, sockAddr); } @@ -418,7 +419,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& emit dataSent(destinationNode.getType(), packet->getDataSize()); destinationNode.recordBytesSent(packet->getDataSize()); - return sendPacket(std::move(packet), *activeSocket, &destinationNode.getAuthenticateHash()); + return sendPacket(std::move(packet), *activeSocket, destinationNode.getConnectionSecret()); } else { qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node" << destinationNode << "- not sending"; return ERROR_SENDING_PACKET_BYTES; @@ -426,18 +427,18 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& } qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const HifiSockAddr& sockAddr, - HMACAuth* hmacAuth) { + const QUuid& connectionSecret) { Q_ASSERT(!packet->isPartOfMessage()); if (packet->isReliable()) { collectPacketStats(*packet); - fillPacketHeader(*packet, hmacAuth); + fillPacketHeader(*packet, connectionSecret); auto size = packet->getDataSize(); _nodeSocket.writePacket(std::move(packet), sockAddr); return size; } else { - return sendUnreliablePacket(*packet, sockAddr, hmacAuth); + return sendUnreliablePacket(*packet, sockAddr, connectionSecret); } } @@ -446,14 +447,13 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi if (activeSocket) { qint64 bytesSent = 0; - auto& connectionHash = destinationNode.getAuthenticateHash(); + auto connectionSecret = destinationNode.getConnectionSecret(); // close the last packet in the list packetList.closeCurrentPacket(); while (!packetList._packets.empty()) { - bytesSent += sendPacket(packetList.takeFront(), *activeSocket, - &connectionHash); + bytesSent += sendPacket(packetList.takeFront(), *activeSocket, connectionSecret); } emit dataSent(destinationNode.getType(), bytesSent); @@ -466,14 +466,14 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi } qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr, - HMACAuth* hmacAuth) { + const QUuid& connectionSecret) { qint64 bytesSent = 0; // close the last packet in the list packetList.closeCurrentPacket(); while (!packetList._packets.empty()) { - bytesSent += sendPacket(packetList.takeFront(), sockAddr, hmacAuth); + bytesSent += sendPacket(packetList.takeFront(), sockAddr, connectionSecret); } return bytesSent; @@ -501,7 +501,7 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr packetList, for (std::unique_ptr& packet : packetList->_packets) { NLPacket* nlPacket = static_cast(packet.get()); collectPacketStats(*nlPacket); - fillPacketHeader(*nlPacket, &destinationNode.getAuthenticateHash()); + fillPacketHeader(*nlPacket, destinationNode.getConnectionSecret()); } return _nodeSocket.writePacketList(std::move(packetList), *activeSocket); @@ -524,7 +524,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& auto& destinationSockAddr = (overridenSockAddr.isNull()) ? *destinationNode.getActiveSocket() : overridenSockAddr; - return sendPacket(std::move(packet), destinationSockAddr, &destinationNode.getAuthenticateHash()); + return sendPacket(std::move(packet), destinationSockAddr, destinationNode.getConnectionSecret()); } int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer message, SharedNodePointer sendingNode) { @@ -610,6 +610,7 @@ bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newCo { QWriteLocker writeLocker(&_nodeMutex); + _localIDMap.unsafe_erase(matchingNode->getLocalID()); _nodeHash.unsafe_erase(it); } @@ -717,10 +718,11 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t // insert the new node and release our read lock #if defined(Q_OS_ANDROID) || (defined(__clang__) && defined(Q_OS_LINUX)) _nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); + _localIDMap.insert(std::pair(localID, newNodePointer)); #else _nodeHash.emplace(newNode->getUUID(), newNodePointer); -#endif _localIDMap.emplace(localID, newNodePointer); +#endif readLocker.unlock(); qCDebug(networking) << "Added" << *newNode; diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 05374bbfbb..3d6fd0cd91 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -138,17 +138,19 @@ public: // use sendUnreliablePacket to send an unreliable packet (that you do not need to move) // either to a node (via its active socket) or to a manual sockaddr qint64 sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode); - qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr); + qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, + const QUuid& connectionSecret = QUuid()); // use sendPacket to send a moved unreliable or reliable NL packet to a node's active socket or manual sockaddr qint64 sendPacket(std::unique_ptr packet, const Node& destinationNode); - qint64 sendPacket(std::unique_ptr packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr); + qint64 sendPacket(std::unique_ptr packet, const HifiSockAddr& sockAddr, + const QUuid& connectionSecret = QUuid()); // use sendUnreliableUnorderedPacketList to unreliably send separate packets from the packet list // either to a node's active socket or to a manual sockaddr qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const Node& destinationNode); qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr, - HMACAuth* hmacAuth = nullptr); + const QUuid& connectionSecret = QUuid()); // use sendPacketList to send reliable packet lists (ordered or unordered) to a node's active socket // or to a manual sock addr @@ -370,7 +372,7 @@ protected: qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr, const QUuid& connectionSecret = QUuid()); void collectPacketStats(const NLPacket& packet); - void fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth = nullptr); + void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret = QUuid()); void setLocalSocket(const HifiSockAddr& sockAddr); diff --git a/libraries/networking/src/NLPacket.cpp b/libraries/networking/src/NLPacket.cpp index 3355e1cd6b..ac3fbc966b 100644 --- a/libraries/networking/src/NLPacket.cpp +++ b/libraries/networking/src/NLPacket.cpp @@ -11,8 +11,6 @@ #include "NLPacket.h" -#include "HMACAuth.h" - int NLPacket::localHeaderSize(PacketType type) { bool nonSourced = PacketTypeEnum::getNonSourcedPackets().contains(type); bool nonVerified = PacketTypeEnum::getNonVerifiedPackets().contains(type); @@ -152,14 +150,18 @@ QByteArray NLPacket::verificationHashInHeader(const udt::Packet& packet) { return QByteArray(packet.getData() + offset, NUM_BYTES_MD5_HASH); } -QByteArray NLPacket::hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash) { +QByteArray NLPacket::hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret) { + QCryptographicHash hash(QCryptographicHash::Md5); + int offset = Packet::totalHeaderSize(packet.isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_LOCALID + NUM_BYTES_MD5_HASH; // add the packet payload and the connection UUID hash.addData(packet.getData() + offset, packet.getDataSize() - offset); - auto hashResult { hash.result() }; - return QByteArray((const char*) hashResult.data(), (int) hashResult.size()); + hash.addData(connectionSecret.toRfc4122()); + + // return the hash + return hash.result(); } void NLPacket::writeTypeAndVersion() { @@ -212,14 +214,14 @@ void NLPacket::writeSourceID(LocalID sourceID) const { _sourceID = sourceID; } -void NLPacket::writeVerificationHash(HMACAuth& hmacAuth) const { +void NLPacket::writeVerificationHashGivenSecret(const QUuid& connectionSecret) const { Q_ASSERT(!PacketTypeEnum::getNonSourcedPackets().contains(_type) && !PacketTypeEnum::getNonVerifiedPackets().contains(_type)); auto offset = Packet::totalHeaderSize(isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_LOCALID; - QByteArray verificationHash = hashForPacketAndHMAC(*this, hmacAuth); + QByteArray verificationHash = hashForPacketAndSecret(*this, connectionSecret); memcpy(_packet.get() + offset, verificationHash.data(), verificationHash.size()); } diff --git a/libraries/networking/src/NLPacket.h b/libraries/networking/src/NLPacket.h index 4103f2068e..9b07bc6581 100644 --- a/libraries/networking/src/NLPacket.h +++ b/libraries/networking/src/NLPacket.h @@ -18,8 +18,6 @@ #include "udt/Packet.h" -class HMACAuth; - class NLPacket : public udt::Packet { Q_OBJECT public: @@ -71,7 +69,7 @@ public: static LocalID sourceIDInHeader(const udt::Packet& packet); static QByteArray verificationHashInHeader(const udt::Packet& packet); - static QByteArray hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash); + static QByteArray hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret); PacketType getType() const { return _type; } void setType(PacketType type); @@ -80,9 +78,9 @@ public: void setVersion(PacketVersion version); LocalID getSourceID() const { return _sourceID; } - + void writeSourceID(LocalID sourceID) const; - void writeVerificationHash(HMACAuth& hmacAuth) const; + void writeVerificationHashGivenSecret(const QUuid& connectionSecret) const; protected: diff --git a/libraries/networking/src/NetworkLogging.h b/libraries/networking/src/NetworkLogging.h index 518c600efe..30116ff405 100644 --- a/libraries/networking/src/NetworkLogging.h +++ b/libraries/networking/src/NetworkLogging.h @@ -17,7 +17,6 @@ Q_DECLARE_LOGGING_CATEGORY(resourceLog) Q_DECLARE_LOGGING_CATEGORY(networking) Q_DECLARE_LOGGING_CATEGORY(asset_client) -Q_DECLARE_LOGGING_CATEGORY(entity_script_client) Q_DECLARE_LOGGING_CATEGORY(messages_client) #endif // hifi_NetworkLogging_h diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 7979b36e30..73b7c44e7e 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -86,10 +86,10 @@ NodeType_t NodeType::fromString(QString type) { Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, - const HifiSockAddr& localSocket, QObject* parent) : + const HifiSockAddr& localSocket, QObject* parent) : NetworkPeer(uuid, publicSocket, localSocket, parent), _type(type), - _authenticateHash(new HMACAuth), _pingMs(-1), // "Uninitialized" + _pingMs(-1), // "Uninitialized" _clockSkewUsec(0), _mutex(), _clockSkewMovingPercentile(30, 0.8f) // moving 80th percentile of 30 samples @@ -108,7 +108,6 @@ void Node::setType(char type) { _symmetricSocket.setObjectName(typeString); } - void Node::updateClockSkewUsec(qint64 clockSkewSample) { _clockSkewMovingPercentile.updatePercentile(clockSkewSample); _clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile(); @@ -195,12 +194,3 @@ QDebug operator<<(QDebug debug, const Node& node) { debug.nospace() << node.getPublicSocket() << "/" << node.getLocalSocket(); return debug.nospace(); } - -void Node::setConnectionSecret(const QUuid& connectionSecret) { - if (_connectionSecret == connectionSecret) { - return; - } - - _connectionSecret = connectionSecret; - _authenticateHash->setKey(_connectionSecret); -} diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 5b3b559582..93b6a649d4 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -33,7 +33,6 @@ #include "SimpleMovingAverage.h" #include "MovingPercentile.h" #include "NodePermissions.h" -#include "HMACAuth.h" class Node : public NetworkPeer { Q_OBJECT @@ -56,8 +55,7 @@ public: void setIsUpstream(bool isUpstream) { _isUpstream = isUpstream; } const QUuid& getConnectionSecret() const { return _connectionSecret; } - void setConnectionSecret(const QUuid& connectionSecret); - HMACAuth& getAuthenticateHash() const { return *_authenticateHash; } + void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } NodeData* getLinkedData() const { return _linkedData.get(); } void setLinkedData(std::unique_ptr linkedData) { _linkedData = std::move(linkedData); } @@ -99,7 +97,6 @@ private: NodeType_t _type; QUuid _connectionSecret; - std::unique_ptr _authenticateHash; std::unique_ptr _linkedData; bool _isReplicated { false }; int _pingMs; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index cfe81a58af..04e32f50cb 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -638,7 +638,7 @@ void NodeList::processDomainServerList(QSharedPointer message) // if this was the first domain-server list from this domain, we've now connected if (!_domainHandler.isConnected()) { - _domainHandler.setLocalID(newLocalID); + _domainHandler.setLocalID(domainLocalID); _domainHandler.setUUID(domainUUID); _domainHandler.setIsConnected(true); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 17531f45b0..609483bc56 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -1,4 +1,4 @@ -// + // // ResourceCache.h // libraries/shared/src // @@ -85,26 +85,29 @@ private: /// Wrapper to expose resources to JS/QML class ScriptableResource : public QObject { + + + /**jsdoc + * @constructor Resource + * @property {string} url - URL of this resource. + * @property {Resource.State} state - Current loading state. + */ + Q_OBJECT Q_PROPERTY(QUrl url READ getURL) Q_PROPERTY(int state READ getState NOTIFY stateChanged) - /**jsdoc - * @constructor Resource - * @property url {string} url of this resource - * @property state {Resource.State} current loading state - */ public: /**jsdoc * @name Resource.State * @static - * @property QUEUED {int} The resource is queued up, waiting to be loaded. - * @property LOADING {int} The resource is downloading - * @property LOADED {int} The resource has finished downloaded by is not complete - * @property FINISHED {int} The resource has completly finished loading and is ready. - * @property FAILED {int} Downloading the resource has failed. + * @property {number} QUEUED - The resource is queued up, waiting to be loaded. + * @property {number} LOADING - The resource is downloading. + * @property {number} LOADED - The resource has finished downloaded by is not complete. + * @property {number} FINISHED - The resource has completely finished loading and is ready. + * @property {number} FAILED - Downloading the resource has failed. */ enum State { @@ -120,7 +123,7 @@ public: virtual ~ScriptableResource() = default; /**jsdoc - * Release this resource + * Release this resource. * @function Resource#release */ Q_INVOKABLE void release(); @@ -135,18 +138,18 @@ public: signals: /**jsdoc - * Signaled when download progress for this resource has changed + * Triggered when download progress for this resource has changed. * @function Resource#progressChanged - * @param bytesReceived {int} bytes downloaded so far - * @param bytesTotal {int} total number of bytes in the resource + * @param {number} bytesReceived - Byytes downloaded so far. + * @param {number} bytesTotal - Total number of bytes in the resource. * @returns {Signal} */ void progressChanged(uint64_t bytesReceived, uint64_t bytesTotal); /**jsdoc - * Signaled when resource loading state has changed + * Triggered when resource loading state has changed. * @function Resource#stateChanged - * @param bytesReceived {Resource.State} new state + * @param {Resource.State} state - New state. * @returns {Signal} */ void stateChanged(int state); @@ -181,50 +184,30 @@ Q_DECLARE_METATYPE(ScriptableResource*); /// Base class for resource caches. class ResourceCache : public QObject { Q_OBJECT + + // JSDoc 3.5.5 doesn't augment namespaces with @property or @function definitions. + // The ResourceCache properties and functions are copied to the different exposed cache classes. + + /**jsdoc + * @property {number} numTotal - Total number of total resources. Read-only. + * @property {number} numCached - Total number of cached resource. Read-only. + * @property {number} sizeTotal - Size in bytes of all resources. Read-only. + * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + */ Q_PROPERTY(size_t numTotal READ getNumTotalResources NOTIFY dirty) Q_PROPERTY(size_t numCached READ getNumCachedResources NOTIFY dirty) Q_PROPERTY(size_t sizeTotal READ getSizeTotalResources NOTIFY dirty) Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty) - /**jsdoc - * @namespace ResourceCache - * @property numTotal {number} total number of total resources - * @property numCached {number} total number of cached resource - * @property sizeTotal {number} size in bytes of all resources - * @property sizeCached {number} size in bytes of all cached resources - */ - public: - /**jsdoc - * Returns the total number of resources - * @function ResourceCache.getNumTotalResources - * @return {number} - */ + size_t getNumTotalResources() const { return _numTotalResources; } - - /**jsdoc - * Returns the total size in bytes of all resources - * @function ResourceCache.getSizeTotalResources - * @return {number} - */ size_t getSizeTotalResources() const { return _totalResourcesSize; } - - /**jsdoc - * Returns the total number of cached resources - * @function ResourceCache.getNumCachedResources - * @return {number} - */ size_t getNumCachedResources() const { return _numUnusedResources; } - - /**jsdoc - * Returns the total size in bytes of cached resources - * @function ResourceCache.getSizeCachedResources - * @return {number} - */ size_t getSizeCachedResources() const { return _unusedResourcesSize; } /**jsdoc - * Returns list of all resource urls + * Get the list of all resource URLs. * @function ResourceCache.getResourceList * @return {string[]} */ @@ -252,28 +235,45 @@ public: void clearUnusedResources(); signals: + + /**jsdoc + * @function ResourceCache.dirty + * @returns {Signal} + */ void dirty(); protected slots: + + /**jsdoc + * @function ResourceCache.updateTotalSize + * @param {number} deltaSize + */ void updateTotalSize(const qint64& deltaSize); + /**jsdoc + * @function ResourceCache.prefetch + * @param {string} url + * @param {object} extra + * @returns {object} + */ // Prefetches a resource to be held by the QScriptEngine. // Left as a protected member so subclasses can overload prefetch // and delegate to it (see TextureCache::prefetch(const QUrl&, int). ScriptableResource* prefetch(const QUrl& url, void* extra); + /**jsdoc + * Asynchronously loads a resource from the specified URL and returns it. + * @function ResourceCache.getResource + * @param {string} url - URL of the resource to load. + * @param {string} [fallback=""] - Fallback URL if load of the desired URL fails. + * @param {} [extra=null] + * @return {Resource} + */ /// Loads a resource from the specified URL and returns it. /// If the caller is on a different thread than the ResourceCache, /// returns an empty smart pointer and loads its asynchronously. /// \param fallback a fallback URL to load if the desired one is unavailable /// \param extra extra data to pass to the creator, if appropriate - /**jsdoc - * Asynchronously loads a resource from the spedified URL and returns it. - * @param url {string} url of resource to load - * @param fallback {string} fallback URL if load of the desired url fails - * @function ResourceCache.getResource - * @return {Resource} - */ QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), void* extra = NULL); @@ -287,8 +287,8 @@ protected: // the QScriptEngine will delete the pointer when it is garbage collected. /**jsdoc * Prefetches a resource. - * @param url {string} url of resource to load * @function ResourceCache.prefetch + * @param {string} url - URL of the resource to prefetch. * @return {Resource} */ Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr); } diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 18e4593c91..8b6de7da11 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -84,7 +84,8 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy _domainServerTimer.start(); // start sending stats packet once we connect to the domain - connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), &_statsTimer, SLOT(start())); + connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, + &_statsTimer, static_cast(&QTimer::start)); // stop sending stats if we disconnect connect(&nodeList->getDomainHandler(), &DomainHandler::disconnectedFromDomain, &_statsTimer, &QTimer::stop); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index db466a0403..98b0e1d892 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -91,7 +91,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::Ping: return static_cast(PingVersion::IncludeConnectionID); default: - return 19; + return 20; } } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 35db43e5e7..2efd32f2e8 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -795,9 +795,10 @@ QString getMarketplaceID(const QString& urlString) { } bool Octree::readFromURL(const QString& urlString) { - QString marketplaceID = getMarketplaceID(urlString); + QString trimmedUrl = urlString.trimmed(); + QString marketplaceID = getMarketplaceID(trimmedUrl); auto request = - std::unique_ptr(DependencyManager::get()->createResourceRequest(this, urlString)); + std::unique_ptr(DependencyManager::get()->createResourceRequest(this, trimmedUrl)); if (!request) { return false; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index ab7c2ec252..3ea3616b15 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -153,6 +153,8 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { // remove the objects (aka MotionStates) from physics _physicsEngine->removeSetOfObjects(_physicalObjects); + clearOwnershipData(); + // delete the MotionStates for (auto stateItr : _physicalObjects) { EntityMotionState* motionState = static_cast(&(*stateItr)); @@ -165,7 +167,6 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { _physicalObjects.clear(); // clear all other lists specific to this derived class - clearOwnershipData(); _entitiesToRemoveFromPhysics.clear(); _entitiesToAddToPhysics.clear(); _incomingChanges.clear(); diff --git a/libraries/physics/src/ShapeFactory.cpp b/libraries/physics/src/ShapeFactory.cpp index f484f32fdf..5abeb022aa 100644 --- a/libraries/physics/src/ShapeFactory.cpp +++ b/libraries/physics/src/ShapeFactory.cpp @@ -114,6 +114,11 @@ btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) { minCorner = glm::min(minCorner, points[i]); } center /= (float)(points.size()); + if (glm::any(glm::isnan(center))) { + // don't feed garbage to Bullet + assert(false); // crash here in DEBUG so we can investigate source of bad input + return nullptr; + } float margin = hull->getMargin(); @@ -265,7 +270,7 @@ btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) { } const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { - btCollisionShape* shape = NULL; + btCollisionShape* shape = nullptr; int type = info.getType(); switch(type) { case SHAPE_TYPE_BOX: { diff --git a/libraries/plugins/src/plugins/InputConfiguration.cpp b/libraries/plugins/src/plugins/InputConfiguration.cpp index 9234ac6585..daa8c6d08c 100644 --- a/libraries/plugins/src/plugins/InputConfiguration.cpp +++ b/libraries/plugins/src/plugins/InputConfiguration.cpp @@ -32,7 +32,8 @@ QStringList InputConfiguration::inputPlugins() { for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { QString pluginName = plugin->getName(); if (pluginName == QString("OpenVR")) { - inputPlugins << QString("Vive"); + QString headsetName = plugin->getDeviceName(); + inputPlugins << headsetName; } else { inputPlugins << pluginName; } @@ -54,7 +55,8 @@ QStringList InputConfiguration::activeInputPlugins() { if (plugin->configurable()) { QString pluginName = plugin->getName(); if (pluginName == QString("OpenVR")) { - activePlugins << QString("Vive"); + QString headsetName = plugin->getDeviceName(); + activePlugins << headsetName; } else { activePlugins << pluginName; } @@ -74,7 +76,7 @@ QString InputConfiguration::configurationLayout(QString pluginName) { QString sourcePath; for (auto plugin : PluginManager::getInstance()->getInputPlugins()) { - if (plugin->getName() == pluginName) { + if (plugin->getName() == pluginName || plugin->getDeviceName() == pluginName) { return plugin->configurationLayout(); } } diff --git a/libraries/plugins/src/plugins/InputPlugin.h b/libraries/plugins/src/plugins/InputPlugin.h index 5d02964c97..23fbb6cac6 100644 --- a/libraries/plugins/src/plugins/InputPlugin.h +++ b/libraries/plugins/src/plugins/InputPlugin.h @@ -26,10 +26,11 @@ public: // If an input plugin is only a single device, it will only return it's primary name. virtual QStringList getSubdeviceNames() { return { getName() }; }; virtual void setConfigurationSettings(const QJsonObject configurationSettings) { } - virtual QJsonObject configurationSettings() { return QJsonObject(); } + virtual QJsonObject configurationSettings() { return QJsonObject(); } virtual QString configurationLayout() { return QString(); } + virtual QString getDeviceName() { return QString(); } virtual void calibrate() {} - virtual bool uncalibrate() { return false; } + virtual bool uncalibrate() { return false; } virtual bool configurable() { return false; } virtual bool isHandController() const { return false; } virtual bool isHeadController() const { return false; } diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 2da1c41340..688f3fdb5f 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -66,14 +66,10 @@ OffscreenSurface::OffscreenSurface() } OffscreenSurface::~OffscreenSurface() { - disconnect(qApp); - _sharedObject->destroy(); + delete _sharedObject; } bool OffscreenSurface::fetchTexture(TextureAndFence& textureAndFence) { - if (!_sharedObject) { - return false; - } hifi::qml::impl::TextureAndFence typedTextureAndFence; bool result = _sharedObject->fetchTexture(typedTextureAndFence); textureAndFence = typedTextureAndFence; diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 6b66ce9314..945a469611 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -49,8 +49,8 @@ RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThre qFatal("Unable to create new offscreen GL context"); } - moveToThread(targetThread); _canvas.moveToThreadWithContext(targetThread); + moveToThread(targetThread); } void RenderEventHandler::onInitalize() { @@ -160,11 +160,8 @@ void RenderEventHandler::onQuit() { } _shared->shutdownRendering(_canvas, _currentSize); - // Release the reference to the shared object. This will allow it to - // be destroyed (should happen on it's own thread). - _shared->deleteLater(); - - deleteLater(); - + _canvas.doneCurrent(); + _canvas.moveToThreadWithContext(qApp->thread()); + moveToThread(qApp->thread()); QThread::currentThread()->quit(); } diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index b2057117f6..2fde057ca8 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -72,28 +72,41 @@ SharedObject::SharedObject() { QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } + SharedObject::~SharedObject() { - if (_quickWindow) { - _quickWindow->destroy(); - _quickWindow = nullptr; + // After destroy returns, the rendering thread should be gone + destroy(); + + // _renderTimer is created with `this` as the parent, so need no explicit destruction + + // Destroy the event hand + if (_renderObject) { + delete _renderObject; + _renderObject = nullptr; } if (_renderControl) { - _renderControl->deleteLater(); + delete _renderControl; _renderControl = nullptr; } - if (_renderThread) { - _renderThread->quit(); - _renderThread->deleteLater(); - } - if (_rootItem) { - _rootItem->deleteLater(); + delete _rootItem; _rootItem = nullptr; } - releaseEngine(_qmlContext->engine()); + if (_quickWindow) { + _quickWindow->destroy(); + delete _quickWindow; + _quickWindow = nullptr; + } + + if (_qmlContext) { + auto engine = _qmlContext->engine(); + delete _qmlContext; + _qmlContext = nullptr; + releaseEngine(engine); + } } void SharedObject::create(OffscreenSurface* surface) { @@ -119,6 +132,10 @@ void SharedObject::create(OffscreenSurface* surface) { } void SharedObject::setRootItem(QQuickItem* rootItem) { + if (_quit) { + return; + } + _rootItem = rootItem; _rootItem->setSize(_quickWindow->size()); @@ -127,7 +144,6 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { _renderThread->setObjectName(objectName()); _renderThread->start(); - // Create event handler for the render thread _renderObject = new RenderEventHandler(this, _renderThread); QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Initialize)); @@ -137,35 +153,43 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { } void SharedObject::destroy() { + // `destroy` is idempotent, it can be called multiple times without issues if (_quit) { return; } if (!_rootItem) { - deleteLater(); return; } - _paused = true; if (_renderTimer) { + _renderTimer->stop(); QObject::disconnect(_renderTimer); - _renderTimer->deleteLater(); } - QObject::disconnect(_renderControl); + if (_renderControl) { + QObject::disconnect(_renderControl); + } + QObject::disconnect(qApp); { QMutexLocker lock(&_mutex); _quit = true; - QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + if (_renderObject) { + QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + } } // Block until the rendering thread has stopped // FIXME this is undesirable because this is blocking the main thread, // but I haven't found a reliable way to do this only at application // shutdown - _renderThread->wait(); + if (_renderThread) { + _renderThread->wait(); + delete _renderThread; + _renderThread = nullptr; + } } @@ -190,9 +214,9 @@ QQmlEngine* SharedObject::acquireEngine(OffscreenSurface* surface) { if (!globalEngine) { Q_ASSERT(0 == globalEngineRefCount); globalEngine = new QQmlEngine(); - surface->initializeQmlEngine(result); - ++globalEngineRefCount; + surface->initializeEngine(result); } + ++globalEngineRefCount; result = globalEngine; #else result = new QQmlEngine(); diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 96e9f4d222..54fdc903ca 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -37,15 +37,25 @@ public: virtual glm::vec3 getAvatarPosition() const = 0; - virtual bool isAboutToQuit() const = 0; - virtual void postLambdaEvent(std::function f) = 0; + // Unfortunately, having this here is a bad idea. Lots of objects connect to + // the aboutToQuit signal, and it's impossible to know the order in which + // the receivers will be called, so this might return false negatives + //virtual bool isAboutToQuit() const = 0; + + // Queue code to execute on the main thread. + // If called from the main thread, the lambda will execute synchronously + virtual void postLambdaEvent(const std::function& f) = 0; + // Synchronously execute code on the main thread. This function will + // not return until the code is executed, regardles of which thread it + // is called from + virtual void sendLambdaEvent(const std::function& f) = 0; virtual qreal getDevicePixelRatio() = 0; virtual render::ScenePointer getMain3DScene() = 0; virtual render::EnginePointer getRenderEngine() = 0; - virtual void pushPostUpdateLambda(void* key, std::function func) = 0; + virtual void pushPostUpdateLambda(void* key, const std::function& func) = 0; virtual bool isHMDMode() const = 0; @@ -54,5 +64,4 @@ public: static void setInstance(AbstractViewStateInterface* instance); }; - -#endif // hifi_AbstractViewStateInterface_h +#endif // hifi_AbstractViewStateInterface_h diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index 90424b04b2..7086b65f4c 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -16,6 +16,7 @@ #include "AbstractViewStateInterface.h" #include "RenderUtilsLogging.h" #include "DebugDraw.h" +#include "StencilMaskPass.h" #include "animdebugdraw_vert.h" #include "animdebugdraw_frag.h" @@ -70,7 +71,7 @@ public: typedef render::Payload AnimDebugDrawPayload; namespace render { - template <> const ItemKey payloadGetKey(const AnimDebugDrawData::Pointer& data) { return (data->_isVisible ? ItemKey::Builder::opaqueShape() : ItemKey::Builder::opaqueShape().withInvisible()).withTagBits(ItemKey::TAG_BITS_ALL); } + template <> const ItemKey payloadGetKey(const AnimDebugDrawData::Pointer& data) { return (data->_isVisible ? ItemKey::Builder::transparentShape() : ItemKey::Builder::transparentShape().withInvisible()).withTagBits(ItemKey::TAG_BITS_ALL); } template <> const Item::Bound payloadGetBound(const AnimDebugDrawData::Pointer& data) { return data->_bound; } template <> void payloadRender(const AnimDebugDrawData::Pointer& data, RenderArgs* args) { data->render(args); @@ -104,6 +105,7 @@ AnimDebugDraw::AnimDebugDraw() : state->setBlendFunction(false, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + PrepareStencil::testMaskDrawShape(*state.get()); auto vertShader = animdebugdraw_vert::getShader(); auto fragShader = animdebugdraw_frag::getShader(); auto program = gpu::Shader::createProgram(vertShader, fragShader); diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index ba5036ad68..dd4bda2e37 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -187,7 +187,8 @@ const int AntialiasingPass_DepthMapSlot = 3; const int AntialiasingPass_NextMapSlot = 4; -Antialiasing::Antialiasing() { +Antialiasing::Antialiasing(bool isSharpenEnabled) : + _isSharpenEnabled{ isSharpenEnabled } { _antialiasingBuffers = std::make_shared(2U); } @@ -197,7 +198,7 @@ Antialiasing::~Antialiasing() { _antialiasingTextures[1].reset(); } -const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(const render::RenderContextPointer& renderContext) { if (!_antialiasingPipeline) { @@ -206,17 +207,6 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("taaParamsBuffer"), AntialiasingPass_ParamsSlot)); - - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AntialiasingPass_FrameTransformSlot)); - - slotBindings.insert(gpu::Shader::Binding(std::string("historyMap"), AntialiasingPass_HistoryMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), AntialiasingPass_SourceMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("velocityMap"), AntialiasingPass_VelocityMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AntialiasingPass_DepthMapSlot)); - - - gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -224,6 +214,21 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { // Good to go add the brand new pipeline _antialiasingPipeline = gpu::Pipeline::create(program, state); + + gpu::doInBatch("SurfaceGeometryPass::CurvaturePipeline", renderContext->args->_context, [program](gpu::Batch& batch) { + batch.runLambda([program]() { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("taaParamsBuffer"), AntialiasingPass_ParamsSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), AntialiasingPass_FrameTransformSlot)); + + slotBindings.insert(gpu::Shader::Binding(std::string("historyMap"), AntialiasingPass_HistoryMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), AntialiasingPass_SourceMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("velocityMap"), AntialiasingPass_VelocityMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), AntialiasingPass_DepthMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + }); + }); } return _antialiasingPipeline; @@ -282,8 +287,11 @@ const gpu::PipelinePointer& Antialiasing::getDebugBlendPipeline() { } void Antialiasing::configure(const Config& config) { - _sharpen = config.sharpen; - _params.edit().blend = config.blend; + _sharpen = config.sharpen * 0.25f; + if (!_isSharpenEnabled) { + _sharpen = 0.0f; + } + _params.edit().blend = config.blend * config.blend; _params.edit().covarianceGamma = config.covarianceGamma; _params.edit().setConstrainColor(config.constrainColor); @@ -328,11 +336,11 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const if (!_antialiasingBuffers->get(0)) { // Link the antialiasing FBO to texture + auto format = sourceBuffer->getRenderBuffer(0)->getTexelFormat(); + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR, gpu::Sampler::WRAP_CLAMP); for (int i = 0; i < 2; i++) { auto& antiAliasingBuffer = _antialiasingBuffers->edit(i); antiAliasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); - auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get()->getLightingTexture()->getTexelFormat(); - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); _antialiasingTextures[i] = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); antiAliasingBuffer->setRenderBuffer(0, _antialiasingTextures[i]); } @@ -343,7 +351,7 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setViewportTransform(args->_viewport); // TAA step - getAntialiasingPipeline(); + getAntialiasingPipeline(renderContext); batch.setResourceFramebufferSwapChainTexture(AntialiasingPass_HistoryMapSlot, _antialiasingBuffers, 0); batch.setResourceTexture(AntialiasingPass_SourceMapSlot, sourceBuffer->getRenderBuffer(0)); batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, velocityBuffer->getVelocityTexture()); @@ -354,7 +362,7 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setUniformBuffer(AntialiasingPass_FrameTransformSlot, deferredFrameTransform->getFrameTransformBuffer()); batch.setFramebufferSwapChain(_antialiasingBuffers, 1); - batch.setPipeline(getAntialiasingPipeline()); + batch.setPipeline(getAntialiasingPipeline(renderContext)); batch.draw(gpu::TRIANGLE_STRIP, 4); // Blend step @@ -380,8 +388,6 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setResourceTexture(AntialiasingPass_VelocityMapSlot, nullptr); batch.setResourceTexture(AntialiasingPass_NextMapSlot, nullptr); }); - - args->popViewFrustum(); } @@ -495,7 +501,7 @@ void JitterSample::configure(const Config& config) { _scale = config.scale; } -void JitterSample::run(const render::RenderContextPointer& renderContext) { +void JitterSample::run(const render::RenderContextPointer& renderContext, Output& jitter) { auto& current = _sampleSequence.currentIndex; if (!_freeze) { if (current >= 0) { @@ -504,39 +510,7 @@ void JitterSample::run(const render::RenderContextPointer& renderContext) { current = -1; } } - auto args = renderContext->args; - auto viewFrustum = args->getViewFrustum(); - - auto jit = _sampleSequence.offsets[(current < 0 ? SEQUENCE_LENGTH : current)]; - auto width = (float)args->_viewport.z; - auto height = (float)args->_viewport.w; - - auto jx = 2.0f * jit.x / width; - auto jy = 2.0f * jit.y / height; - - if (!args->isStereo()) { - auto projMat = viewFrustum.getProjection(); - - projMat[2][0] += jx; - projMat[2][1] += jy; - - viewFrustum.setProjection(projMat); - viewFrustum.calculate(); - args->pushViewFrustum(viewFrustum); - } else { - mat4 projMats[2]; - args->_context->getStereoProjections(projMats); - - jx *= 2.0f; - - for (int i = 0; i < 2; i++) { - auto& projMat = projMats[i]; - projMat[2][0] += jx; - projMat[2][1] += jy; - } - - args->_context->setStereoProjections(projMats); - } + jitter = _sampleSequence.offsets[(current < 0 ? SEQUENCE_LENGTH : current)]; } diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index 03fdb9d9a4..9f62fd76c1 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -58,14 +58,15 @@ class JitterSample { public: enum { - SEQUENCE_LENGTH = 128 + SEQUENCE_LENGTH = 64 }; using Config = JitterSampleConfig; - using JobModel = render::Job::Model; + using Output = glm::vec2; + using JobModel = render::Job::ModelO; void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext); + void run(const render::RenderContextPointer& renderContext, Output& jitter); private: @@ -105,11 +106,11 @@ class AntialiasingConfig : public render::Job::Config { public: AntialiasingConfig() : render::Job::Config(true) {} - float blend{ 0.05f }; - float sharpen{ 0.15f }; + float blend{ 0.25f }; + float sharpen{ 0.05f }; bool constrainColor{ true }; - float covarianceGamma{ 0.9f }; + float covarianceGamma{ 0.65f }; bool feedbackColor{ false }; float debugX{ 0.0f }; @@ -131,7 +132,7 @@ signals: struct TAAParams { float nope{ 0.0f }; - float blend{ 0.05f }; + float blend{ 0.15f }; float covarianceGamma{ 1.0f }; float debugShowVelocityThreshold{ 1.0f }; @@ -168,12 +169,12 @@ public: using Config = AntialiasingConfig; using JobModel = render::Job::ModelI; - Antialiasing(); + Antialiasing(bool isSharpenEnabled = true); ~Antialiasing(); void configure(const Config& config); void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); - const gpu::PipelinePointer& getAntialiasingPipeline(); + const gpu::PipelinePointer& getAntialiasingPipeline(const render::RenderContextPointer& renderContext); const gpu::PipelinePointer& getBlendPipeline(); const gpu::PipelinePointer& getDebugBlendPipeline(); @@ -189,6 +190,7 @@ private: TAAParamsBuffer _params; float _sharpen{ 0.15f }; int _sharpenLoc{ -1 }; + bool _isSharpenEnabled{ true }; }; diff --git a/libraries/render-utils/src/BloomApply.slf b/libraries/render-utils/src/BloomApply.slf index 953258e8ab..961438888e 100644 --- a/libraries/render-utils/src/BloomApply.slf +++ b/libraries/render-utils/src/BloomApply.slf @@ -13,7 +13,7 @@ uniform sampler2D blurMap0; uniform sampler2D blurMap1; uniform sampler2D blurMap2; -uniform float intensity; +uniform vec3 intensity; in vec2 varTexCoord0; out vec4 outFragColor; @@ -23,5 +23,5 @@ void main(void) { vec4 blur1 = texture(blurMap1, varTexCoord0); vec4 blur2 = texture(blurMap2, varTexCoord0); - outFragColor = vec4((blur0.rgb+blur1.rgb+blur2.rgb)*intensity, 1.0f); + outFragColor = vec4(blur0.rgb*intensity.x + blur1.rgb*intensity.y + blur2.rgb*intensity.z, 1.0f); } diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index ddd63f012f..ee06e17578 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -50,7 +50,7 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons if (!_outputBuffer || _outputBuffer->getSize() != bufferSize) { auto colorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(inputBuffer->getTexelFormat(), bufferSize.x, bufferSize.y, - gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT))); + gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT, gpu::Sampler::WRAP_CLAMP))); _outputBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("BloomThreshold")); _outputBuffer->setRenderBuffer(0, colorTexture); @@ -93,12 +93,14 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons outputs = _outputBuffer; } -BloomApply::BloomApply() { +BloomApply::BloomApply() : _intensities{ 1.0f, 1.0f, 1.0f } { } void BloomApply::configure(const Config& config) { - _intensity = config.intensity; + _intensities.x = config.intensity / 3.0f; + _intensities.y = _intensities.x; + _intensities.z = _intensities.x; } void BloomApply::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { @@ -106,10 +108,10 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; - static auto BLUR0_SLOT = 0; - static auto BLUR1_SLOT = 1; - static auto BLUR2_SLOT = 2; - static auto INTENSITY_SLOT = 3; + static const auto BLUR0_SLOT = 0; + static const auto BLUR1_SLOT = 1; + static const auto BLUR2_SLOT = 2; + static const auto INTENSITY_SLOT = 3; if (!_pipeline) { auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); @@ -149,7 +151,7 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In batch.setResourceTexture(BLUR0_SLOT, blur0FB->getRenderBuffer(0)); batch.setResourceTexture(BLUR1_SLOT, blur1FB->getRenderBuffer(0)); batch.setResourceTexture(BLUR2_SLOT, blur2FB->getRenderBuffer(0)); - batch._glUniform1f(INTENSITY_SLOT, _intensity / 3.0f); + batch._glUniform3f(INTENSITY_SLOT, _intensities.x, _intensities.y, _intensities.z); batch.draw(gpu::TRIANGLE_STRIP, 4); }); } @@ -306,19 +308,19 @@ float BloomConfig::getIntensity() const { void BloomConfig::setSize(float value) { std::string blurName{ "BloomBlurN" }; auto sigma = 0.5f+value*3.5f; + auto task = static_cast(_task); for (auto i = 0; i < BLOOM_BLUR_LEVEL_COUNT; i++) { blurName.back() = '0' + i; - auto task = static_cast(_task); auto blurJobIt = task->editJob(blurName); assert(blurJobIt != task->_jobs.end()); auto& gaussianBlur = blurJobIt->edit(); auto gaussianBlurParams = gaussianBlur.getParameters(); - gaussianBlurParams->setFilterGaussianTaps(5, sigma); - // Gaussian blur increases at each level to have a slower rolloff on the edge - // of the response - sigma *= 1.5f; + gaussianBlurParams->setFilterGaussianTaps(9, sigma); } + auto blurJobIt = task->getJob("BloomApply"); + assert(blurJobIt != task->_jobs.end()); + blurJobIt->getConfiguration()->setProperty("sigma", sigma); } Bloom::Bloom() { @@ -350,7 +352,7 @@ void Bloom::build(JobModel& task, const render::Varying& inputs, render::Varying // Mix all blur levels at quarter resolution const auto applyInput = BloomApply::Inputs(bloomInputBuffer, blurFB0, blurFB1, blurFB2).asVarying(); task.addJob("BloomApply", applyInput); - // And them blend result in additive manner on top of final color buffer + // And then blend result in additive manner on top of final color buffer const auto drawInput = BloomDraw::Inputs(frameBuffer, bloomInputBuffer).asVarying(); task.addJob("BloomDraw", drawInput); diff --git a/libraries/render-utils/src/BloomEffect.h b/libraries/render-utils/src/BloomEffect.h index 5352c65e4d..2ff6bc35a7 100644 --- a/libraries/render-utils/src/BloomEffect.h +++ b/libraries/render-utils/src/BloomEffect.h @@ -25,7 +25,7 @@ public: BloomConfig() : render::Task::Config(false) {} - float size{ 0.8f }; + float size{ 0.7f }; void setIntensity(float value); float getIntensity() const; @@ -41,7 +41,7 @@ class BloomThresholdConfig : public render::Job::Config { public: - float threshold{ 1.25f }; + float threshold{ 0.9f }; signals: void dirty(); @@ -71,10 +71,12 @@ private: class BloomApplyConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(float intensity MEMBER intensity NOTIFY dirty) + Q_PROPERTY(float sigma MEMBER sigma NOTIFY dirty) public: - float intensity{ 0.8f }; + float intensity{ 0.25f }; + float sigma{ 1.0f }; signals: void dirty(); @@ -94,7 +96,7 @@ public: private: gpu::PipelinePointer _pipeline; - float _intensity{ 1.0f }; + glm::vec3 _intensities; }; class BloomDraw { diff --git a/libraries/render-utils/src/DeferredFrameTransform.cpp b/libraries/render-utils/src/DeferredFrameTransform.cpp index d1c51bf46f..21d5b120d6 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.cpp +++ b/libraries/render-utils/src/DeferredFrameTransform.cpp @@ -18,7 +18,7 @@ DeferredFrameTransform::DeferredFrameTransform() { _frameTransformBuffer = gpu::BufferView(std::make_shared(sizeof(FrameTransform), (const gpu::Byte*) &frameTransform)); } -void DeferredFrameTransform::update(RenderArgs* args) { +void DeferredFrameTransform::update(RenderArgs* args, glm::vec2 jitter) { // Update the depth info with near and far (same for stereo) auto nearZ = args->getViewFrustum().getNearClip(); @@ -38,12 +38,23 @@ void DeferredFrameTransform::update(RenderArgs* args) { args->getViewFrustum().evalProjectionMatrix(frameTransformBuffer.projectionMono); + // There may be some sort of mismatch here if the viewport size isn't the same as the frame buffer size as + // jitter is normalized by frame buffer size in TransformCamera. But we should be safe. + jitter.x /= args->_viewport.z; + jitter.y /= args->_viewport.w; + // Running in stereo ? bool isStereo = args->isStereo(); if (!isStereo) { - frameTransformBuffer.projection[0] = frameTransformBuffer.projectionMono; + frameTransformBuffer.projectionUnjittered[0] = frameTransformBuffer.projectionMono; + frameTransformBuffer.invProjectionUnjittered[0] = glm::inverse(frameTransformBuffer.projectionUnjittered[0]); + frameTransformBuffer.stereoInfo = glm::vec4(0.0f, (float)args->_viewport.z, 0.0f, 0.0f); frameTransformBuffer.invpixelInfo = glm::vec4(1.0f / args->_viewport.z, 1.0f / args->_viewport.w, 0.0f, 0.0f); + + frameTransformBuffer.projection[0] = frameTransformBuffer.projectionUnjittered[0]; + frameTransformBuffer.projection[0][2][0] += jitter.x; + frameTransformBuffer.projection[0][2][1] += jitter.y; frameTransformBuffer.invProjection[0] = glm::inverse(frameTransformBuffer.projection[0]); } else { @@ -52,22 +63,28 @@ void DeferredFrameTransform::update(RenderArgs* args) { args->_context->getStereoProjections(projMats); args->_context->getStereoViews(eyeViews); + jitter.x *= 2.0f; + for (int i = 0; i < 2; i++) { // Compose the mono Eye space to Stereo clip space Projection Matrix auto sideViewMat = projMats[i] * eyeViews[i]; - frameTransformBuffer.projection[i] = sideViewMat; - frameTransformBuffer.invProjection[i] = glm::inverse(sideViewMat); - } + frameTransformBuffer.projectionUnjittered[i] = sideViewMat; + frameTransformBuffer.invProjectionUnjittered[i] = glm::inverse(sideViewMat); + + frameTransformBuffer.projection[i] = frameTransformBuffer.projectionUnjittered[i]; + frameTransformBuffer.projection[i][2][0] += jitter.x; + frameTransformBuffer.projection[i][2][1] += jitter.y; + frameTransformBuffer.invProjection[i] = glm::inverse(frameTransformBuffer.projection[i]); + } frameTransformBuffer.stereoInfo = glm::vec4(1.0f, (float)(args->_viewport.z >> 1), 0.0f, 1.0f); frameTransformBuffer.invpixelInfo = glm::vec4(1.0f / (float)(args->_viewport.z >> 1), 1.0f / args->_viewport.w, 0.0f, 0.0f); - } } -void GenerateDeferredFrameTransform::run(const render::RenderContextPointer& renderContext, DeferredFrameTransformPointer& frameTransform) { +void GenerateDeferredFrameTransform::run(const render::RenderContextPointer& renderContext, const Input& jitter, Output& frameTransform) { if (!frameTransform) { frameTransform = std::make_shared(); } - frameTransform->update(renderContext->args); + frameTransform->update(renderContext->args, jitter); } diff --git a/libraries/render-utils/src/DeferredFrameTransform.h b/libraries/render-utils/src/DeferredFrameTransform.h index 8c2f0a7321..f8181b6b8a 100644 --- a/libraries/render-utils/src/DeferredFrameTransform.h +++ b/libraries/render-utils/src/DeferredFrameTransform.h @@ -25,7 +25,7 @@ public: DeferredFrameTransform(); - void update(RenderArgs* args); + void update(RenderArgs* args, glm::vec2 jitter); UniformBufferView getFrameTransformBuffer() const { return _frameTransformBuffer; } @@ -43,16 +43,20 @@ protected: glm::vec4 depthInfo; // Stereo info is { isStereoFrame, halfWidth } glm::vec4 stereoInfo{ 0.0 }; - // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space - glm::mat4 projection[2]; - // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space - glm::mat4 invProjection[2]; - // THe mono projection for sure + // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space + glm::mat4 projection[2]; + // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space + glm::mat4 invProjection[2]; + // THe mono projection for sure glm::mat4 projectionMono; // Inv View matrix from eye space (mono) to world space glm::mat4 invView; // View matrix from world space to eye space (mono) glm::mat4 view; + // Mono proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space without jittering + glm::mat4 projectionUnjittered[2]; + // Inverse proj matrix or Left and Right proj matrix going from Mono Eye space to side clip space without jittering + glm::mat4 invProjectionUnjittered[2]; FrameTransform() {} }; @@ -68,11 +72,14 @@ using DeferredFrameTransformPointer = std::shared_ptr; class GenerateDeferredFrameTransform { public: - using JobModel = render::Job::ModelO; + + using Input = glm::vec2; + using Output = DeferredFrameTransformPointer; + using JobModel = render::Job::ModelIO; GenerateDeferredFrameTransform() {} - void run(const render::RenderContextPointer& renderContext, DeferredFrameTransformPointer& frameTransform); + void run(const render::RenderContextPointer& renderContext, const Input& jitter, Output& frameTransform); private: }; diff --git a/libraries/render-utils/src/DeferredTransform.slh b/libraries/render-utils/src/DeferredTransform.slh index 6b0e1cd642..2f015b95fe 100644 --- a/libraries/render-utils/src/DeferredTransform.slh +++ b/libraries/render-utils/src/DeferredTransform.slh @@ -35,6 +35,8 @@ struct DeferredFrameTransform { mat4 _projectionMono; mat4 _viewInverse; mat4 _view; + mat4 _projectionUnJittered[2]; + mat4 _invProjectionUnJittered[2]; }; uniform deferredFrameTransformBuffer { @@ -62,6 +64,12 @@ mat4 getProjection(int side) { mat4 getProjectionMono() { return frameTransform._projectionMono; } +mat4 getUnjitteredProjection(int side) { + return frameTransform._projectionUnJittered[side]; +} +mat4 getUnjitteredInvProjection(int side) { + return frameTransform._invProjectionUnJittered[side]; +} // positive near distance of the projection float getProjectionNear() { @@ -138,6 +146,14 @@ vec3 evalEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { return eyePos.xyz / eyePos.w; } +vec3 evalUnjitteredEyePositionFromZdb(int side, float Zdb, vec2 texcoord) { + // compute the view space position using the depth + vec3 clipPos; + clipPos.xyz = vec3(texcoord.xy, Zdb) * 2.0 - 1.0; + vec4 eyePos = frameTransform._invProjectionUnJittered[side] * vec4(clipPos.xyz, 1.0); + return eyePos.xyz / eyePos.w; +} + vec3 evalEyePositionFromZeye(int side, float Zeye, vec2 texcoord) { float Zdb = evalZdbFromZeye(Zeye); return evalEyePositionFromZdb(side, Zdb, texcoord); diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index ecfa9881a3..8be142d939 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -2207,7 +2207,7 @@ static void buildWebShader(const gpu::ShaderPointer& vertShader, const gpu::Shad gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - PrepareStencil::testMaskDrawShape(*state); + PrepareStencil::testMaskDrawShapeNoAA(*state); pipelinePointerOut = gpu::Pipeline::create(shaderPointerOut, state); } diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh index 14e8b0ef07..4bcd74eefe 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -11,7 +11,6 @@ <@if not MODEL_MATERIAL_TEXTURES_SLH@> <@def MODEL_MATERIAL_TEXTURES_SLH@> - <@func declareMaterialTexMapArrayBuffer()@> const int MAX_TEXCOORDS = 2; @@ -48,6 +47,8 @@ TexMapArray getTexMapArray() { <@func declareMaterialTextures(withAlbedo, withRoughness, withNormal, withMetallic, withEmissive, withOcclusion, withScattering)@> +#define TAA_TEXTURE_LOD_BIAS -1.0 + <@include gpu/TextureTable.slh@> #ifdef GPU_TEXTURE_TABLE_BINDLESS @@ -66,6 +67,7 @@ TextureTable(0, matTex); <@if withAlbedo@> #define albedoMap 0 vec4 fetchAlbedoMap(vec2 uv) { + // Should take into account TAA_TEXTURE_LOD_BIAS? return tableTexValue(matTex, albedoMap, uv); } <@endif@> @@ -73,6 +75,7 @@ vec4 fetchAlbedoMap(vec2 uv) { <@if withRoughness@> #define roughnessMap 4 float fetchRoughnessMap(vec2 uv) { + // Should take into account TAA_TEXTURE_LOD_BIAS? return tableTexValue(matTex, roughnessMap, uv).r; } <@endif@> @@ -80,6 +83,7 @@ float fetchRoughnessMap(vec2 uv) { <@if withNormal@> #define normalMap 1 vec3 fetchNormalMap(vec2 uv) { + // Should take into account TAA_TEXTURE_LOD_BIAS? return tableTexValue(matTex, normalMap, uv).xyz; } <@endif@> @@ -87,6 +91,7 @@ vec3 fetchNormalMap(vec2 uv) { <@if withMetallic@> #define metallicMap 2 float fetchMetallicMap(vec2 uv) { + // Should take into account TAA_TEXTURE_LOD_BIAS? return tableTexValue(matTex, metallicMap, uv).r; } <@endif@> @@ -94,6 +99,7 @@ float fetchMetallicMap(vec2 uv) { <@if withEmissive@> #define emissiveMap 3 vec3 fetchEmissiveMap(vec2 uv) { + // Should take into account TAA_TEXTURE_LOD_BIAS? return tableTexValue(matTex, emissiveMap, uv).rgb; } <@endif@> @@ -119,14 +125,14 @@ float fetchScatteringMap(vec2 uv) { <@if withAlbedo@> uniform sampler2D albedoMap; vec4 fetchAlbedoMap(vec2 uv) { - return texture(albedoMap, uv); + return texture(albedoMap, uv, TAA_TEXTURE_LOD_BIAS); } <@endif@> <@if withRoughness@> uniform sampler2D roughnessMap; float fetchRoughnessMap(vec2 uv) { - return (texture(roughnessMap, uv).r); + return (texture(roughnessMap, uv, TAA_TEXTURE_LOD_BIAS).r); } <@endif@> @@ -134,7 +140,7 @@ float fetchRoughnessMap(vec2 uv) { uniform sampler2D normalMap; vec3 fetchNormalMap(vec2 uv) { // unpack normal, swizzle to get into hifi tangent space with Y axis pointing out - vec2 t = 2.0 * (texture(normalMap, uv).rg - vec2(0.5, 0.5)); + vec2 t = 2.0 * (texture(normalMap, uv, TAA_TEXTURE_LOD_BIAS).rg - vec2(0.5, 0.5)); vec2 t2 = t*t; return vec3(t.x, sqrt(1.0 - t2.x - t2.y), t.y); } @@ -143,14 +149,14 @@ vec3 fetchNormalMap(vec2 uv) { <@if withMetallic@> uniform sampler2D metallicMap; float fetchMetallicMap(vec2 uv) { - return (texture(metallicMap, uv).r); + return (texture(metallicMap, uv, TAA_TEXTURE_LOD_BIAS).r); } <@endif@> <@if withEmissive@> uniform sampler2D emissiveMap; vec3 fetchEmissiveMap(vec2 uv) { - return texture(emissiveMap, uv).rgb; + return texture(emissiveMap, uv, TAA_TEXTURE_LOD_BIAS).rgb; } <@endif@> @@ -164,7 +170,7 @@ float fetchOcclusionMap(vec2 uv) { <@if withScattering@> uniform sampler2D scatteringMap; float fetchScatteringMap(vec2 uv) { - float scattering = texture(scatteringMap, uv).r; // boolean scattering for now + float scattering = texture(scatteringMap, uv, TAA_TEXTURE_LOD_BIAS).r; // boolean scattering for now return max(((scattering - 0.1) / 0.9), 0.0); return texture(scatteringMap, uv).r; // boolean scattering for now } @@ -234,8 +240,8 @@ vec3 fetchLightmapMap(vec2 uv) { vec3 normalizedTangent = normalize(<$interpolatedTangent$>.xyz); vec3 normalizedBitangent = cross(normalizedNormal, normalizedTangent); // attenuate the normal map divergence from the mesh normal based on distance - // The attenuation range [20,100] meters from the eye is arbitrary for now - vec3 localNormal = mix(<$fetchedNormal$>, vec3(0.0, 1.0, 0.0), smoothstep(20.0, 100.0, (-<$fragPosES$>).z)); + // The attenuation range [30,100] meters from the eye is arbitrary for now + vec3 localNormal = mix(<$fetchedNormal$>, vec3(0.0, 1.0, 0.0), smoothstep(30.0, 100.0, (-<$fragPosES$>).z)); <$normal$> = vec3(normalizedBitangent * localNormal.x + normalizedNormal * localNormal.y + normalizedTangent * localNormal.z); } <@endfunc@> diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 70f873734a..65b12ac0d4 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -182,9 +182,7 @@ const float SCALE_CHANGE_EPSILON = 0.0000001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { _scale = scale; - if (_scale.x == 0.0f || _scale.y == 0.0f || _scale.z == 0.0f) { - assert(false); - } + assert(_scale.x != 0.0f && scale.y != 0.0f && scale.z != 0.0f); simulate(0.0f, true); } } diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index d54cefa5c7..293393550b 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -46,6 +46,7 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& const auto& inItems = inputs.get0(); const auto& lightingModel = inputs.get1(); + const auto jitter = inputs.get2(); config->setNumDrawn((int)inItems.size()); emit config->numDrawnChanged(); @@ -75,7 +76,8 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); - batch.setViewTransform(viewMat); + batch.setProjectionJitter(jitter.x, jitter.y); + batch.setViewTransform(viewMat); // Setup lighting model for all items; batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index 767b6893cd..79599d3ab7 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -60,7 +60,7 @@ protected: class DrawOverlay3D { public: - using Inputs = render::VaryingSet2 ; + using Inputs = render::VaryingSet3; using Config = DrawOverlay3DConfig; using JobModel = render::Job::ModelI; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 2377f5131f..bea298122e 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -95,10 +95,10 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren fadeEffect->build(task, opaques); - task.addJob("JitterCam"); + const auto jitter = task.addJob("JitterCam"); // Prepare deferred, generate the shared Deferred Frame Transform - const auto deferredFrameTransform = task.addJob("DeferredFrameTransform"); + const auto deferredFrameTransform = task.addJob("DeferredFrameTransform", jitter); const auto lightingModel = task.addJob("LightingModel"); @@ -116,12 +116,11 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("PrepareStencil", primaryFramebuffer); // Render opaque objects in DeferredBuffer - const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel).asVarying(); + const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, jitter).asVarying(); task.addJob("DrawOpaqueDeferred", opaqueInputs, shapePlumber); task.addJob("OpaqueRangeTimer", opaqueRangeTimer); - // Opaque all rendered // Linear Depth Pass @@ -188,8 +187,37 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DebugLightClusters", debugLightClustersInputs); } + const auto outlineRangeTimer = task.addJob("BeginHighlightRangeTimer", "Highlight"); + // Select items that need to be outlined + const auto selectionBaseName = "contextOverlayHighlightList"; + const auto selectedItems = addSelectItemJobs(task, selectionBaseName, metas, opaques, transparents); + + const auto outlineInputs = DrawHighlightTask::Inputs(items.get0(), deferredFramebuffer, lightingFramebuffer, deferredFrameTransform).asVarying(); + task.addJob("DrawHighlight", outlineInputs); + + task.addJob("HighlightRangeTimer", outlineRangeTimer); + + const auto overlaysInFrontRangeTimer = task.addJob("BeginOverlaysInFrontRangeTimer", "BeginOverlaysInFrontRangeTimer"); + + // Layered Overlays + const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); + const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); + const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); + const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); + + const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, jitter).asVarying(); + const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, jitter).asVarying(); + task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); + task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); + + task.addJob("OverlaysInFrontRangeTimer", overlaysInFrontRangeTimer); + const auto toneAndPostRangeTimer = task.addJob("BeginToneAndPostRangeTimer", "PostToneOverlaysAntialiasing"); + // AA job before bloom to limit flickering + const auto antialiasingInputs = Antialiasing::Inputs(deferredFrameTransform, lightingFramebuffer, linearDepthTarget, velocityBuffer).asVarying(); + task.addJob("Antialiasing", antialiasingInputs); + // Add bloom const auto bloomInputs = Bloom::Inputs(deferredFrameTransform, lightingFramebuffer).asVarying(); task.addJob("Bloom", bloomInputs); @@ -198,16 +226,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer).asVarying(); task.addJob("ToneMapping", toneMappingInputs); - const auto outlineRangeTimer = task.addJob("BeginHighlightRangeTimer", "Highlight"); - // Select items that need to be outlined - const auto selectionBaseName = "contextOverlayHighlightList"; - const auto selectedItems = addSelectItemJobs(task, selectionBaseName, metas, opaques, transparents); - - const auto outlineInputs = DrawHighlightTask::Inputs(items.get0(), deferredFramebuffer, primaryFramebuffer, deferredFrameTransform).asVarying(); - task.addJob("DrawHighlight", outlineInputs); - - task.addJob("HighlightRangeTimer", outlineRangeTimer); - { // Debug the bounds of the rendered items, still look at the zbuffer task.addJob("DrawMetaBounds", metas); task.addJob("DrawOpaqueBounds", opaques); @@ -230,26 +248,11 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawSelectionBounds", selectedItems); } - // Layered Overlays - const auto filteredOverlaysOpaque = task.addJob("FilterOverlaysLayeredOpaque", overlayOpaques, Item::LAYER_3D_FRONT); - const auto filteredOverlaysTransparent = task.addJob("FilterOverlaysLayeredTransparent", overlayTransparents, Item::LAYER_3D_FRONT); - const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN(0); - const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN(0); - - const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel).asVarying(); - const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel).asVarying(); - task.addJob("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); - task.addJob("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); - { // Debug the bounds of the rendered Overlay items that are marked drawInFront, still look at the zbuffer task.addJob("DrawOverlayInFrontOpaqueBounds", overlaysInFrontOpaque); task.addJob("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); } - // AA job - const auto antialiasingInputs = Antialiasing::Inputs(deferredFrameTransform, primaryFramebuffer, linearDepthTarget, velocityBuffer).asVarying(); - task.addJob("Antialiasing", antialiasingInputs); - // Debugging stages { // Debugging Deferred buffer job @@ -285,9 +288,10 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto overlaysHUDOpaque = filteredOverlaysOpaque.getN(1); const auto overlaysHUDTransparent = filteredOverlaysTransparent.getN(1); + const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); - const auto overlayHUDOpaquesInputs = DrawOverlay3D::Inputs(overlaysHUDOpaque, lightingModel).asVarying(); - const auto overlayHUDTransparentsInputs = DrawOverlay3D::Inputs(overlaysHUDTransparent, lightingModel).asVarying(); + const auto overlayHUDOpaquesInputs = DrawOverlay3D::Inputs(overlaysHUDOpaque, lightingModel, nullJitter).asVarying(); + const auto overlayHUDTransparentsInputs = DrawOverlay3D::Inputs(overlaysHUDTransparent, lightingModel, nullJitter).asVarying(); task.addJob("DrawOverlayHUDOpaque", overlayHUDOpaquesInputs, true); task.addJob("DrawOverlayHUDTransparent", overlayHUDTransparentsInputs, false); @@ -379,6 +383,7 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const const auto& inItems = inputs.get0(); const auto& lightingModel = inputs.get1(); + const auto jitter = inputs.get2(); RenderArgs* args = renderContext->args; @@ -395,6 +400,7 @@ void DrawStateSortDeferred::run(const RenderContextPointer& renderContext, const args->getViewFrustum().evalViewTransform(viewMat); batch.setProjectionTransform(projMat); + batch.setProjectionJitter(jitter.x, jitter.y); batch.setViewTransform(viewMat); // Setup lighting model for all items; diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 1b57c88768..b923ec0878 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -81,7 +81,7 @@ protected: class DrawStateSortDeferred { public: - using Inputs = render::VaryingSet2; + using Inputs = render::VaryingSet3; using Config = DrawStateSortConfig; using JobModel = render::Job::ModelI; diff --git a/libraries/render-utils/src/SurfaceGeometryPass.cpp b/libraries/render-utils/src/SurfaceGeometryPass.cpp index d3bf3ab198..51046f10b3 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.cpp +++ b/libraries/render-utils/src/SurfaceGeometryPass.cpp @@ -168,7 +168,7 @@ void LinearDepthPass::run(const render::RenderContextPointer& renderContext, con outputs.edit4() = halfNormalTexture; auto linearDepthPipeline = getLinearDepthPipeline(renderContext); - auto downsamplePipeline = getDownsamplePipeline(); + auto downsamplePipeline = getDownsamplePipeline(renderContext); auto depthViewport = args->_viewport; auto halfViewport = depthViewport >> 1; @@ -241,19 +241,12 @@ const gpu::PipelinePointer& LinearDepthPass::getLinearDepthPipeline(const render } -const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline() { +const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline(const render::RenderContextPointer& renderContext) { if (!_downsamplePipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto ps = surfaceGeometry_downsampleDepthNormal_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), DepthLinearPass_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("linearDepthMap"), DepthLinearPass_DepthMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), DepthLinearPass_NormalMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); PrepareStencil::testShape(*state); @@ -261,6 +254,16 @@ const gpu::PipelinePointer& LinearDepthPass::getDownsamplePipeline() { // Good to go add the brand new pipeline _downsamplePipeline = gpu::Pipeline::create(program, state); + + gpu::doInBatch("LinearDepthPass::run", renderContext->args->_context, [program](gpu::Batch& batch) { + batch.runLambda([program]() { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", DepthLinearPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding("linearDepthMap", DepthLinearPass_DepthMapSlot)); + slotBindings.insert(gpu::Shader::Binding("normalMap", DepthLinearPass_NormalMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + }); + }); } return _downsamplePipeline; diff --git a/libraries/render-utils/src/SurfaceGeometryPass.h b/libraries/render-utils/src/SurfaceGeometryPass.h index 501cf3fa87..367f599f67 100644 --- a/libraries/render-utils/src/SurfaceGeometryPass.h +++ b/libraries/render-utils/src/SurfaceGeometryPass.h @@ -84,7 +84,7 @@ private: const gpu::PipelinePointer& getLinearDepthPipeline(const render::RenderContextPointer& renderContext); gpu::PipelinePointer _linearDepthPipeline; - const gpu::PipelinePointer& getDownsamplePipeline(); + const gpu::PipelinePointer& getDownsamplePipeline(const render::RenderContextPointer& renderContext); gpu::PipelinePointer _downsamplePipeline; gpu::RangeTimerPointer _gpuTimer; diff --git a/libraries/render-utils/src/VelocityBufferPass.cpp b/libraries/render-utils/src/VelocityBufferPass.cpp index 78471d48af..3f7da4cdcd 100644 --- a/libraries/render-utils/src/VelocityBufferPass.cpp +++ b/libraries/render-utils/src/VelocityBufferPass.cpp @@ -113,7 +113,7 @@ void VelocityBufferPass::run(const render::RenderContextPointer& renderContext, outputs.edit1() = velocityFBO; outputs.edit2() = velocityTexture; - auto cameraMotionPipeline = getCameraMotionPipeline(); + auto cameraMotionPipeline = getCameraMotionPipeline(renderContext); auto fullViewport = args->_viewport; @@ -143,18 +143,12 @@ void VelocityBufferPass::run(const render::RenderContextPointer& renderContext, } -const gpu::PipelinePointer& VelocityBufferPass::getCameraMotionPipeline() { +const gpu::PipelinePointer& VelocityBufferPass::getCameraMotionPipeline(const render::RenderContextPointer& renderContext) { if (!_cameraMotionPipeline) { auto vs = gpu::StandardShaderLib::getDrawViewportQuadTransformTexcoordVS(); auto ps = velocityBuffer_cameraMotion_frag::getShader(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), VelocityBufferPass_FrameTransformSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), VelocityBufferPass_DepthMapSlot)); - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); // Stencil test the curvature pass for objects pixels only, not the background @@ -164,6 +158,16 @@ const gpu::PipelinePointer& VelocityBufferPass::getCameraMotionPipeline() { // Good to go add the brand new pipeline _cameraMotionPipeline = gpu::Pipeline::create(program, state); + + gpu::doInBatch("VelocityBufferPass::CameraMotionPipeline", renderContext->args->_context, + [program](gpu::Batch& batch) { + batch.runLambda([program]() { + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), VelocityBufferPass_FrameTransformSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("depthMap"), VelocityBufferPass_DepthMapSlot)); + gpu::Shader::makeProgram(*program, slotBindings); + }); + }); } return _cameraMotionPipeline; diff --git a/libraries/render-utils/src/VelocityBufferPass.h b/libraries/render-utils/src/VelocityBufferPass.h index fb2b729368..50b994f6db 100644 --- a/libraries/render-utils/src/VelocityBufferPass.h +++ b/libraries/render-utils/src/VelocityBufferPass.h @@ -79,7 +79,7 @@ private: VelocityFramebufferPointer _velocityFramebuffer; - const gpu::PipelinePointer& getCameraMotionPipeline(); + const gpu::PipelinePointer& getCameraMotionPipeline(const render::RenderContextPointer& renderContext); gpu::PipelinePointer _cameraMotionPipeline; gpu::RangeTimerPointer _gpuTimer; diff --git a/libraries/render-utils/src/fxaa_blend.slf b/libraries/render-utils/src/fxaa_blend.slf index 7a10cecb94..996344c881 100644 --- a/libraries/render-utils/src/fxaa_blend.slf +++ b/libraries/render-utils/src/fxaa_blend.slf @@ -35,6 +35,9 @@ void main(void) { pixels[7] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(0,1), 0); pixels[8] = texelFetch(colorTexture, ivec2(gl_FragCoord.xy)+ivec2(1,1), 0); - sharpenedPixel = pixels[4]*7.8 - (pixels[1]+pixels[3]+pixels[5]+pixels[7]) - (pixels[0]+pixels[2]+pixels[6]+pixels[8])*0.7; - outFragColor = mix(pixels[4], sharpenedPixel, sharpenIntensity); + sharpenedPixel = pixels[4]*6.8 - (pixels[1]+pixels[3]+pixels[5]+pixels[7]) - (pixels[0]+pixels[2]+pixels[6]+pixels[8])*0.7; + + vec4 minColor = max(vec4(0), pixels[4]-vec4(0.5)); + vec4 maxColor = pixels[4]+vec4(0.5); + outFragColor = clamp(pixels[4] + sharpenedPixel * sharpenIntensity, minColor, maxColor); } diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index 2742341628..08e50ee8c5 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -20,14 +20,15 @@ uniform vec4 Color; in vec3 _normalWS; in vec2 _texCoord0; -const float gamma = 2.2; -const float smoothing = 32.0; +#define TAA_TEXTURE_LOD_BIAS -3.0 + const float interiorCutoff = 0.8; const float outlineExpansion = 0.2; +const float taaBias = pow(2.0, TAA_TEXTURE_LOD_BIAS); -void main() { +float evalSDF(vec2 texCoord) { // retrieve signed distance - float sdf = texture(Font, _texCoord0).g; + float sdf = textureLod(Font, texCoord, TAA_TEXTURE_LOD_BIAS).g; if (Outline) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; @@ -35,12 +36,22 @@ void main() { sdf += outlineExpansion; } } - // perform adaptive anti-aliasing of the edges - // The larger we're rendering, the less anti-aliasing we need - float s = smoothing * length(fwidth(_texCoord0)); - float w = clamp(s, 0.0, 0.5); - float a = smoothstep(0.5 - w, 0.5 + w, sdf); - + // Rely on TAA for anti-aliasing + return step(0.5, sdf); +} + +void main() { + vec2 dxTexCoord = dFdx(_texCoord0) * 0.5 * taaBias; + vec2 dyTexCoord = dFdy(_texCoord0) * 0.5 * taaBias; + + // Perform 4x supersampling for anisotropic filtering + float a; + a = evalSDF(_texCoord0); + a += evalSDF(_texCoord0+dxTexCoord); + a += evalSDF(_texCoord0+dyTexCoord); + a += evalSDF(_texCoord0+dxTexCoord+dyTexCoord); + a *= 0.25; + // discard if invisible if (a < 0.01) { discard; diff --git a/libraries/render-utils/src/taa.slh b/libraries/render-utils/src/taa.slh index 583e2978c4..3ca5b10d57 100644 --- a/libraries/render-utils/src/taa.slh +++ b/libraries/render-utils/src/taa.slh @@ -76,47 +76,39 @@ vec2 taa_getRegionFXAA() { #define USE_YCOCG 1 vec4 taa_fetchColor(sampler2D map, vec2 uv) { + vec4 c = texture(map, uv); + // Apply rapid pseudo tonemapping as TAA is applied to a tonemapped image, using luminance as weight, as proposed in + // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf + float lum = dot(vec3(0.3,0.5,0.2),c.rgb); + c.rgb = c.rgb / (1.0+lum); #if USE_YCOCG - vec4 c = texture(map, uv); - return vec4(color_LinearToYCoCg(c.rgb), c.a); + return vec4(color_LinearToYCoCg(c.rgb), c.a); #else - return texture(map, uv); + return c; #endif } vec3 taa_resolveColor(vec3 color) { #if USE_YCOCG - return color_YCoCgToLinear(color); -#else - return color; + color = max(vec3(0), color_YCoCgToUnclampedLinear(color)); #endif + // Apply rapid inverse tonemapping, using luminance as weight, as proposed in + // https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf + float lum = dot(vec3(0.3,0.5,0.2),color.rgb); + color = color / (1.0-lum); + return color; } vec4 taa_fetchSourceMap(vec2 uv) { -#if USE_YCOCG - vec4 c = texture(sourceMap, uv); - return vec4(color_LinearToYCoCg(c.rgb), c.a); -#else - return texture(sourceMap, uv); -#endif + return taa_fetchColor(sourceMap, uv); } vec4 taa_fetchHistoryMap(vec2 uv) { -#if USE_YCOCG - vec4 c = texture(historyMap, uv); - return vec4(color_LinearToYCoCg(c.rgb), c.a); -#else - return texture(historyMap, uv); -#endif + return taa_fetchColor(historyMap, uv); } vec4 taa_fetchNextMap(vec2 uv) { -#if USE_YCOCG - vec4 c = texture(nextMap, uv); - return vec4(color_LinearToYCoCg(c.rgb), c.a); -#else - return texture(nextMap, uv); -#endif + return taa_fetchColor(nextMap, uv); } vec2 taa_fetchVelocityMap(vec2 uv) { diff --git a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf index 22a95b55d1..548feb87dc 100644 --- a/libraries/render-utils/src/velocityBuffer_cameraMotion.slf +++ b/libraries/render-utils/src/velocityBuffer_cameraMotion.slf @@ -28,11 +28,11 @@ void main(void) { float Zdb = texelFetch(depthMap, ivec2(gl_FragCoord.xy), 0).x; // The position of the pixel fragment in Eye space then in world space - vec3 eyePos = evalEyePositionFromZdb(stereoSide.x, Zdb, texcoordPos); + vec3 eyePos = evalUnjitteredEyePositionFromZdb(stereoSide.x, Zdb, texcoordPos); vec3 worldPos = (getViewInverse() * vec4(eyePos, 1.0)).xyz; vec3 prevEyePos = (getPreviousView() * vec4(worldPos, 1.0)).xyz; - vec4 prevClipPos = (frameTransform._projection[stereoSide.x] * vec4(prevEyePos, 1.0)); + vec4 prevClipPos = (getUnjitteredProjection(stereoSide.x) * vec4(prevEyePos, 1.0)); vec2 prevUV = 0.5 * (prevClipPos.xy / prevClipPos.w) + vec2(0.5); //vec2 imageSize = getWidthHeight(0); diff --git a/libraries/render/src/render/BlurTask.cpp b/libraries/render/src/render/BlurTask.cpp index 4c3c52e07b..896482eb2a 100644 --- a/libraries/render/src/render/BlurTask.cpp +++ b/libraries/render/src/render/BlurTask.cpp @@ -163,7 +163,7 @@ bool BlurInOutResource::updateResources(const gpu::FramebufferPointer& sourceFra //if (sourceFramebuffer->hasDepthStencil()) { // _blurredFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat()); //} - auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT); + auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT, gpu::Sampler::WRAP_CLAMP); auto blurringTarget = gpu::Texture::createRenderBuffer(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), blurBufferSize.x, blurBufferSize.y, gpu::Texture::SINGLE_MIP, blurringSampler); _blurredFramebuffer->setRenderBuffer(0, blurringTarget); } @@ -186,7 +186,7 @@ bool BlurInOutResource::updateResources(const gpu::FramebufferPointer& sourceFra /* if (sourceFramebuffer->hasDepthStencil()) { _outputFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat()); }*/ - auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT); + auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT, gpu::Sampler::WRAP_CLAMP); auto blurringTarget = gpu::Texture::createRenderBuffer(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), blurBufferSize.x, blurBufferSize.y, gpu::Texture::SINGLE_MIP, blurringSampler); _outputFramebuffer->setRenderBuffer(0, blurringTarget); } diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index fdce173dfe..5cb1136b74 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -25,6 +25,7 @@ #include /**jsdoc + * The Assets API allows you to communicate with the Asset Browser. * @namespace Assets */ class AssetScriptingInterface : public BaseAssetScriptingInterface, QScriptable { @@ -40,41 +41,34 @@ public: * @param data {string} content to upload * @param callback {Assets~uploadDataCallback} called when upload is complete */ - /**jsdoc * Called when uploadData is complete * @callback Assets~uploadDataCallback * @param {string} url * @param {string} hash */ - Q_INVOKABLE void uploadData(QString data, QScriptValue callback); /**jsdoc * Download data from the connected domain's asset server. * @function Assets.downloadData - * @static - * @param url {string} url of asset to download, must be atp scheme url. + * @param url {string} URL of asset to download, must be ATP scheme URL. * @param callback {Assets~downloadDataCallback} */ - /**jsdoc * Called when downloadData is complete * @callback Assets~downloadDataCallback * @param data {string} content that was downloaded */ - Q_INVOKABLE void downloadData(QString url, QScriptValue downloadComplete); /**jsdoc * Sets up a path to hash mapping within the connected domain's asset server * @function Assets.setMapping - * @static * @param path {string} * @param hash {string} * @param callback {Assets~setMappingCallback} */ - /**jsdoc * Called when setMapping is complete * @callback Assets~setMappingCallback @@ -85,19 +79,27 @@ public: /**jsdoc * Look up a path to hash mapping within the connected domain's asset server * @function Assets.getMapping - * @static * @param path {string} * @param callback {Assets~getMappingCallback} */ - /**jsdoc * Called when getMapping is complete. * @callback Assets~getMappingCallback - * @param error {string} error description if the path could not be resolved; otherwise a null value. * @param assetID {string} hash value if found, else an empty string + * @param error {string} error description if the path could not be resolved; otherwise a null value. */ Q_INVOKABLE void getMapping(QString path, QScriptValue callback); + /**jsdoc + * @function Assets.setBakingEnabled + * @param path {string} + * @param enabled {boolean} + * @param callback {} + */ + /**jsdoc + * Called when setBakingEnabled is complete. + * @callback Assets~setBakingEnabledCallback + */ Q_INVOKABLE void setBakingEnabled(QString path, bool enabled, QScriptValue callback); #if (PR_BUILD || DEV_BUILD) @@ -108,33 +110,38 @@ public: * Request Asset data from the ATP Server * @function Assets.getAsset * @param {URL|Assets.GetOptions} options An atp: style URL, hash, or relative mapped path; or an {@link Assets.GetOptions} object with request parameters - * @param {Assets~getAssetCallback} scope[callback] A scope callback function to receive (error, results) values + * @param {Assets~getAssetCallback} scope A scope callback function to receive (error, results) values + * @param {function} [callback=undefined] */ - /**jsdoc - * A set of properties that can be passed to {@link Assets.getAsset}. - * @typedef {Object} Assets.GetOptions - * @property {URL} [url] an "atp:" style URL, hash, or relative mapped path to fetch - * @property {string} [responseType=text] the desired reponse type (text | arraybuffer | json) - * @property {boolean} [decompress=false] whether to attempt gunzip decompression on the fetched data - * See: {@link Assets.putAsset} and its .compress=true option - */ + + /**jsdoc + * A set of properties that can be passed to {@link Assets.getAsset}. + * @typedef {Object} Assets.GetOptions + * @property {string} [url] an "atp:" style URL, hash, or relative mapped path to fetch + * @property {string} [responseType=text] the desired reponse type (text | arraybuffer | json) + * @property {boolean} [decompress=false] whether to attempt gunzip decompression on the fetched data + * See: {@link Assets.putAsset} and its .compress=true option + */ + /**jsdoc * Called when Assets.getAsset is complete. * @callback Assets~getAssetCallback * @param {string} error - contains error message or null value if no error occured fetching the asset * @param {Asset~getAssetResult} result - result object containing, on success containing asset metadata and contents */ - /**jsdoc - * Result value returned by {@link Assets.getAsset}. - * @typedef {Object} Assets~getAssetResult - * @property {url} [url] the resolved "atp:" style URL for the fetched asset - * @property {string} [hash] the resolved hash for the fetched asset - * @property {string|ArrayBuffer|Object} [response] response data (possibly converted per .responseType value) - * @property {string} [responseType] response type (text | arraybuffer | json) - * @property {string} [contentType] detected asset mime-type (autodetected) - * @property {number} [byteLength] response data size in bytes - * @property {number} [decompressed] flag indicating whether data was decompressed - */ + + /**jsdoc + * Result value returned by {@link Assets.getAsset}. + * @typedef {Object} Assets~getAssetResult + * @property {string} [url] the resolved "atp:" style URL for the fetched asset + * @property {string} [hash] the resolved hash for the fetched asset + * @property {string|ArrayBuffer|Object} [response] response data (possibly converted per .responseType value) + * @property {string} [responseType] response type (text | arraybuffer | json) + * @property {string} [contentType] detected asset mime-type (autodetected) + * @property {number} [byteLength] response data size in bytes + * @property {number} [decompressed] flag indicating whether data was decompressed + */ + Q_INVOKABLE void getAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); /**jsdoc @@ -142,47 +149,133 @@ public: * @function Assets.putAsset * @param {Assets.PutOptions} options A PutOptions object with upload parameters * @param {Assets~putAssetCallback} scope[callback] A scoped callback function invoked with (error, results) + * @param {function} [callback=undefined] */ - /**jsdoc - * A set of properties that can be passed to {@link Assets.putAsset}. - * @typedef {Object} Assets.PutOptions - * @property {ArrayBuffer|string} [data] byte buffer or string value representing the new asset's content - * @property {string} [path=null] ATP path mapping to automatically create (upon successful upload to hash) - * @property {boolean} [compress=false] whether to gzip compress data before uploading - */ + + /**jsdoc + * A set of properties that can be passed to {@link Assets.putAsset}. + * @typedef {Object} Assets.PutOptions + * @property {ArrayBuffer|string} [data] byte buffer or string value representing the new asset's content + * @property {string} [path=null] ATP path mapping to automatically create (upon successful upload to hash) + * @property {boolean} [compress=false] whether to gzip compress data before uploading + */ + /**jsdoc * Called when Assets.putAsset is complete. * @callback Assets~puttAssetCallback * @param {string} error - contains error message (or null value if no error occured while uploading/mapping the new asset) * @param {Asset~putAssetResult} result - result object containing error or result status of asset upload */ - /**jsdoc - * Result value returned by {@link Assets.putAsset}. - * @typedef {Object} Assets~putAssetResult - * @property {url} [url] the resolved "atp:" style URL for the uploaded asset (based on .path if specified, otherwise on the resulting ATP hash) - * @property {string} [path] the uploaded asset's resulting ATP path (or undefined if no path mapping was assigned) - * @property {string} [hash] the uploaded asset's resulting ATP hash - * @property {boolean} [compressed] flag indicating whether the data was compressed before upload - * @property {number} [byteLength] flag indicating final byte size of the data uploaded to the ATP server - */ + + /**jsdoc + * Result value returned by {@link Assets.putAsset}. + * @typedef {Object} Assets~putAssetResult + * @property {string} [url] the resolved "atp:" style URL for the uploaded asset (based on .path if specified, otherwise on the resulting ATP hash) + * @property {string} [path] the uploaded asset's resulting ATP path (or undefined if no path mapping was assigned) + * @property {string} [hash] the uploaded asset's resulting ATP hash + * @property {boolean} [compressed] flag indicating whether the data was compressed before upload + * @property {number} [byteLength] flag indicating final byte size of the data uploaded to the ATP server + */ + Q_INVOKABLE void putAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + /**jsdoc + * @function Assets.deleteAsset + * @property {} options + * @property {} scope + * @property {} [callback = ""] + */ + Q_INVOKABLE void deleteAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + + /**jsdoc + * @function Assets.resolveAsset + * @property {} options + * @property {} scope + * @property {} [callback = ""] + */ + Q_INVOKABLE void resolveAsset(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + + /**jsdoc + * @function Assets.decompressData + * @property {} options + * @property {} scope + * @property {} [callback = ""] + */ + Q_INVOKABLE void decompressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + + /**jsdoc + * @function Assets.compressData + * @property {} options + * @property {} scope + * @property {} [callback = ""] + */ + Q_INVOKABLE void compressData(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + + /**jsdoc + * @function Assets.initializeCache + * @returns {boolean} + */ Q_INVOKABLE bool initializeCache() { return Parent::initializeCache(); } + + /**jsdoc + * @function Assets.canWriteCacheValue + * @property {string} url + * @returns {boolean} + */ Q_INVOKABLE bool canWriteCacheValue(const QUrl& url); + + /**jsdoc + * @function Assets.getCacheStatus + * @property {} scope + * @property {} [callback=undefined] + */ Q_INVOKABLE void getCacheStatus(QScriptValue scope, QScriptValue callback = QScriptValue()) { jsPromiseReady(Parent::getCacheStatus(), scope, callback); } + /**jsdoc + * @function Assets.queryCacheMeta + * @property {} options + * @property {} scope + * @property {} [callback=undefined] + */ + Q_INVOKABLE void queryCacheMeta(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + + /**jsdoc + * @function Assets.loadFromCache + * @property {} options + * @property {} scope + * @property {} [callback=undefined] + */ + Q_INVOKABLE void loadFromCache(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + + /**jsdoc + * @function Assets.saveToCache + * @property {} options + * @property {} scope + * @property {} [callback=undefined] + */ + Q_INVOKABLE void saveToCache(QScriptValue options, QScriptValue scope, QScriptValue callback = QScriptValue()); + + /**jsdoc + * @function Assets.saveToCache + * @property {} url + * @property {} data + * @property {} metadata + * @property {} scope + * @property {} [callback=undefined] + */ + Q_INVOKABLE void saveToCache(const QUrl& url, const QByteArray& data, const QVariantMap& metadata, QScriptValue scope, QScriptValue callback = QScriptValue()); protected: diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index be2b4ebc8c..36fe29243d 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -31,21 +31,88 @@ protected: AudioScriptingInterface() {} // these methods are protected to stop C++ callers from calling, but invokable from script + + /**jsdoc + * @function Audio.playSound + * @param {} sound + * @param {} [injectorOptions=null] + * @returns {object} + */ Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); + + /**jsdoc + * @function Audio.playSystemSound + * @param {} sound + * @param {} position + * @returns {object} + */ // FIXME: there is no way to play a positionless sound Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position); + /**jsdoc + * @function Audio.setStereoInput + * @param {boolean} stereo + * @returns {boolean} + */ Q_INVOKABLE bool setStereoInput(bool stereo); + + /**jsdoc + * @function Audio.isStereoInput + * @returns {boolean} + */ Q_INVOKABLE bool isStereoInput(); signals: - void mutedByMixer(); /// the client has been muted by the mixer - void environmentMuted(); /// the entire environment has been muted by the mixer - void receivedFirstPacket(); /// the client has received its first packet from the audio mixer - void disconnected(); /// the client has been disconnected from the audio mixer - void noiseGateOpened(); /// the noise gate has opened - void noiseGateClosed(); /// the noise gate has closed - void inputReceived(const QByteArray& inputSamples); /// a frame of mic input audio has been received and processed + + /**jsdoc + * The client has been muted by the mixer. + * @function Audio.mutedByMixer + * @returns {Signal} + */ + void mutedByMixer(); + + /**jsdoc + * The entire environment has been muted by the mixer. + * @function Audio.environmentMuted + * @returns {Signal} + */ + void environmentMuted(); + + /**jsdoc + * The client has received its first packet from the audio mixer. + * @function Audio.receivedFirstPacket + * @returns {Signal} + */ + void receivedFirstPacket(); + + /**jsdoc + * The client has been disconnected from the audio mixer. + * @function Audio.disconnected + * @returns {Signal} + */ + void disconnected(); + + /**jsdoc + * The noise gate has opened. + * @function Audio.noiseGateOpened + * @returns {Signal} + */ + void noiseGateOpened(); + + /**jsdoc + * The noise gate has closed. + * @function Audio.noiseGateClosed + * @returns {Signal} + */ + void noiseGateClosed(); + + /**jsdoc + * A frame of mic input audio has been received and processed. + * @function Audio.inputReceived + * @param {} inputSamples + * @returns {Signal} + */ + void inputReceived(const QByteArray& inputSamples); private: AbstractAudioInterface* _localAudioInterface { nullptr }; diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 22e4d30830..0e4f90b928 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -23,6 +23,9 @@ class QScriptEngine; class QScriptValue; +/**jsdoc + * @namespace Recording + */ class RecordingScriptingInterface : public QObject, public Dependency { Q_OBJECT @@ -32,43 +35,196 @@ public: void setScriptEngine(QSharedPointer scriptEngine) { _scriptEngine = scriptEngine; } public slots: + + /**jsdoc + * @function Recording.loadRecording + * @param {string} url + * @param {Recording~loadRecordingCallback} [callback=null] + */ + /**jsdoc + * Called when {@link Recording.loadRecording} is complete. + * @callback Recording~loadRecordingCallback + * @param {boolean} success + * @param {string} url + */ void loadRecording(const QString& url, QScriptValue callback = QScriptValue()); + + /**jsdoc + * @function Recording.startPlaying + */ void startPlaying(); + + /**jsdoc + * @function Recording.pausePlayer + */ void pausePlayer(); + + /**jsdoc + * @function Recording.stopPlaying + */ void stopPlaying(); + + /**jsdoc + * @function Recording.isPlaying + * @returns {boolean} + */ bool isPlaying() const; + + /**jsdoc + * @function Recording.isPaused + * @returns {boolean} + */ bool isPaused() const; + + /**jsdoc + * @function Recording.playerElapsed + * @returns {number} + */ float playerElapsed() const; + + /**jsdoc + * @function Recording.playerLength + * @returns {number} + */ float playerLength() const; + + /**jsdoc + * @function Recording.setPlayerVolume + * @param {number} volume + */ void setPlayerVolume(float volume); + + /**jsdoc + * @function Recording.setPlayerAudioOffset + * @param {number} audioOffset + */ void setPlayerAudioOffset(float audioOffset); + + /**jsdoc + * @function Recording.setPlayerTime + * @param {number} time + */ void setPlayerTime(float time); + + /**jsdoc + * @function Recording.setPlayerLoop + * @param {boolean} loop + */ void setPlayerLoop(bool loop); + + /**jsdoc + * @function Recording.setPlayerUseDisplayName + * @param {boolean} useDisplayName + */ void setPlayerUseDisplayName(bool useDisplayName); + + /**jsdoc + * @function Recording.setPlayerUseAttachments + * @param {boolean} useAttachments + */ void setPlayerUseAttachments(bool useAttachments); + + /**jsdoc + * @function Recording.setPlayerUseHeadModel + * @param {boolean} useHeadModel + * @todo Note: This function currently has no effect. + */ void setPlayerUseHeadModel(bool useHeadModel); + + /**jsdoc + * @function Recording.setPlayerUseSkeletonModel + * @param {boolean} useSkeletonModel + * @todo Note: This function currently doesn't work. + */ void setPlayerUseSkeletonModel(bool useSkeletonModel); + + /**jsdoc + * @function Recording.setPlayFromCurrentLocation + * @param {boolean} playFromCurrentLocation + */ void setPlayFromCurrentLocation(bool playFromCurrentLocation); + + /**jsdoc + * @function Recording.getPlayerUseDisplayName + * @returns {boolean} + */ bool getPlayerUseDisplayName() { return _useDisplayName; } + + /**jsdoc + * @function Recording.getPlayerUseAttachments + * @returns {boolean} + */ bool getPlayerUseAttachments() { return _useAttachments; } + + /**jsdoc + * @function Recording.getPlayerUseHeadModel + * @returns {boolean} + */ bool getPlayerUseHeadModel() { return _useHeadModel; } + + /**jsdoc + * @function Recording.getPlayerUseSkeletonModel + * @returns {boolean} + */ bool getPlayerUseSkeletonModel() { return _useSkeletonModel; } + + /**jsdoc + * @function Recording.getPlayFromCurrentLocation + * @returns {boolean} + */ bool getPlayFromCurrentLocation() { return _playFromCurrentLocation; } + + /**jsdoc + * @function Recording.startRecording + */ void startRecording(); + + /**jsdoc + * @function Recording.stopRecording + */ void stopRecording(); + + /**jsdoc + * @function Recording.isRecording + * @returns {boolean} + */ bool isRecording() const; + + /**jsdoc + * @function Recording.recorderElapsed + * @returns {number} + */ float recorderElapsed() const; + + /**jsdoc + * @function Recording.getDefaultRecordingSaveDirectory + * @returns {string} + */ QString getDefaultRecordingSaveDirectory(); + + /**jsdoc + * @function Recording.saveRecording + * @param {string} filename + */ void saveRecording(const QString& filename); + + /**jsdoc + * @function Recording.saveRecordingToAsset + * @param {function} getClipAtpUrl + */ bool saveRecordingToAsset(QScriptValue getClipAtpUrl); + + /**jsdoc + * @function Recording.loadLastRecording + */ void loadLastRecording(); protected: diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index 07b8c22ca6..c69cd7090d 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -19,6 +19,13 @@ // TODO: if QT moc ever supports nested classes, subclass these to the interface instead of namespacing namespace SceneScripting { + + /**jsdoc + * @typedef Scene.Stage.Location + * @property {number} longitude + * @property {number} latitude + * @property {number} altitude + */ class Location : public QObject { Q_OBJECT @@ -41,6 +48,11 @@ namespace SceneScripting { }; using LocationPointer = std::unique_ptr; + /**jsdoc + * @typedef Scene.Stage.Time + * @property {number} hour + * @property {number} day + */ class Time : public QObject { Q_OBJECT @@ -60,6 +72,13 @@ namespace SceneScripting { }; using TimePointer = std::unique_ptr

    Note: Can only be connected to via this.preload = function (...) { ... } in the entity script.

    + *
    Available in:Client Entity ScriptsServer Entity Scripts
    + * @function Entities.preload + * @param {Uuid} entityID - The ID of the entity that the script is running in. + * @returns {Signal} + * @example Get the ID of the entity that a client entity script is running in. + * var entityScript = (function () { + * this.entityID = Uuid.NULL; + * + * this.preload = function (entityID) { + * this.entityID = entityID; + * print("Entity ID: " + this.entityID); + * }; + * ); + * + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * color: { red: 255, green: 0, blue: 0 }, + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); + */ // since all of these operations can be asynch we will always do the actual work in the response handler // for the download void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success , const QString& status) { @@ -2345,6 +2371,13 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co processDeferredEntityLoads(entityScript, entityID); } +/**jsdoc + * Triggered when the script terminates for a user. + *

    Note: Can only be connected to via this.unoad = function () { ... } in the entity script.

    + *
    Available in:Client Entity ScriptsServer Entity Scripts
    + * @function Entities.unload + * @returns {Signal} + */ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 7f69eee990..63a4ba4f90 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -87,6 +87,10 @@ public: QUrl definingSandboxURL { QUrl("about:EntityScript") }; }; +/**jsdoc + * @namespace Script + * @property {string} context + */ class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider { Q_OBJECT Q_PROPERTY(QString context READ getContext) @@ -115,6 +119,11 @@ public: QString getFilename() const; + /**jsdoc + * Stop the current script. + * @function Script.stop + * @param {boolean} [marshal=false] + */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts Q_INVOKABLE void stop(bool marshal = false); @@ -126,26 +135,69 @@ public: // NOTE - these are NOT intended to be public interfaces available to scripts, the are only Q_INVOKABLE so we can // properly ensure they are only called on the correct thread + /**jsdoc + * @function Script.registerGlobalObject + * @param {string} name + * @param {object} object + */ /// registers a global object by name Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object); + /**jsdoc + * @function Script.registerGetterSetter + * @param {string} name + * @param {object} getter + * @param {object} setter + * @param {string} [parent=""] + */ /// registers a global getter/setter Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, QScriptEngine::FunctionSignature setter, const QString& parent = QString("")); + /**jsdoc + * @function Script.registerFunction + * @param {string} name + * @param {object} function + * @param {number} [numArguments=-1] + */ /// register a global function Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); + /**jsdoc + * @function Script.registerFunction + * @param {string} parent + * @param {string} name + * @param {object} function + * @param {number} [numArguments=-1] + */ /// register a function as a method on a previously registered global object Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); + /**jsdoc + * @function Script.registerValue + * @param {string} name + * @param {object} value + */ /// registers a global object by name Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value); + /**jsdoc + * @function Script.evaluate + * @param {string} program + * @param {string} filename + * @param {number} [lineNumber=-1] + * @returns {object} + */ /// evaluate some code in the context of the ScriptEngine and return the result Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget + /**jsdoc + * @function Script.evaluateInClosure + * @param {object} locals + * @param {object} program + * @returns {object} + */ Q_INVOKABLE QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program); /// if the script engine is not already running, this will download the URL and start the process of seting it up @@ -154,24 +206,122 @@ public: void loadURL(const QUrl& scriptURL, bool reload); bool hasValidScriptSuffix(const QString& scriptFileName); + /**jsdoc + * @function Script.getContext + * @returns {string} + */ Q_INVOKABLE QString getContext() const; + + /**jsdoc + * @function Script.isClientScript + * @returns {boolean} + */ Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; } + + /**jsdoc + * @function Script.isEntityClientScript + * @returns {boolean} + */ Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; } + + /**jsdoc + * @function Script.isEntityServerScript + * @returns {boolean} + */ Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; } + + /**jsdoc + * @function Script.isAgentScript + * @returns {boolean} + */ Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - these are intended to be public interfaces available to scripts + + /**jsdoc + * @function Script.addEventHandler + * @param {Uuid} entityID + * @param {string} eventName + * @param {function} handler + */ Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + + /**jsdoc + * @function Script.removeEventHandler + * @param {Uuid} entityID + * @param {string} eventName + * @param {function} handler + */ Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); + /**jsdoc + * Start a new Interface or entity script. + * @function Script.load + * @param {string} filename - The URL of the script to load. Can be relative to the current script. + * @example Load a script from another script. + * // First file: scriptA.js + * print("This is script A"); + * + * // Second file: scriptB.js + * print("This is script B"); + * Script.load("scriptA.js"); + * + * // If you run scriptB.js you should see both scripts in the running scripts list. + * // And you should see the following output: + * // This is script B + * // This is script A + */ Q_INVOKABLE void load(const QString& loadfile); + + /**jsdoc + * Include JavaScript from other files in the current script. If a callback is specified the files are loaded and included + * asynchronously, otherwise they are included synchronously (i.e., script execution blocks while the files are included). + * @function Script.include + * @param {string[]} filenames - The URLs of the scripts to include. Each can be relative to the current script. + * @param {function} [callback=null] - The function to call back when the scripts have been included. Can be an in-line + * function or the name of a function. + */ Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue()); + + /**jsdoc + * Include JavaScript from another file in the current script. If a callback is specified the file is loaded and included + * asynchronously, otherwise it is included synchronously (i.e., script execution blocks while the file is included). + * @function Script.include + * @param {string} filename - The URL of the script to include. Can be relative to the current script. + * @param {function} [callback=null] - The function to call back when the script has been included. Can be an in-line + * function or the name of a function. + * @example Include a script file asynchronously. + * // First file: scriptA.js + * print("This is script A"); + * + * // Second file: scriptB.js + * print("This is script B"); + * Script.include("scriptA.js", function () { + * print("Script A has been included"); + * }); + * + * // If you run scriptB.js you should see only scriptB.js in the running scripts list. + * // And you should see the following output: + * // This is script B + * // This is script A + * // Script A has been included + */ Q_INVOKABLE void include(const QString& includeFile, QScriptValue callback = QScriptValue()); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MODULE related methods + + /**jsdoc + * @function Script.require + * @param {string} module + */ Q_INVOKABLE QScriptValue require(const QString& moduleId); + + /**jsdoc + * @function Script.resetModuleCache + * @param {boolean} [deleteScriptCache=false] + */ Q_INVOKABLE void resetModuleCache(bool deleteScriptCache = false); QScriptValue currentModule(); bool registerModuleWithParent(const QScriptValue& module, const QScriptValue& parent); @@ -179,34 +329,168 @@ public: QVariantMap fetchModuleSource(const QString& modulePath, const bool forceDownload = false); QScriptValue instantiateModule(const QScriptValue& module, const QString& sourceCode); + /**jsdoc + * Call a function at a set interval. + * @function Script.setInterval + * @param {function} function - The function to call. Can be an in-line function or the name of a function. + * @param {number} interval - The interval at which to call the function, in ms. + * @returns {object} A handle to the interval timer. Can be used by {@link Script.clearInterval}. + * @example Print a message every second. + * Script.setInterval(function () { + * print("Timer fired"); + * }, 1000); + */ Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS); + + /**jsdoc + * Call a function after a delay. + * @function Script.setTimeout + * @param {function} function - The function to call. Can be an in-line function or the name of a function. + * @param {number} timeout - The delay after which to call the function, in ms. + * @returns {object} A handle to the timeout timer. Can be used by {@link Script.clearTimeout}. + * @example Print a message after a second. + * Script.setTimeout(function () { + * print("Timer fired"); + * }, 1000); + */ Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS); + + /**jsdoc + * Stop an interval timer set by {@link Script.setInterval|setInterval}. + * @function Script.clearInterval + * @param {object} timer - The interval timer to clear. + * @example Stop an interval timer. + * // Print a message every second. + * var timer = Script.setInterval(function () { + * print("Timer fired"); + * }, 1000); + * + * // Stop the timer after 10 seconds. + * Script.setTimeout(function () { + * print("Stop timer"); + * Script.clearInterval(timer); + * }, 10000); + */ Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + + /**jsdoc + * Clear a timeout timer set by {@link Script.setTimeout|setTimeout}. + * @function Script.clearTimeout + * @param {object} timer - The timeout timer to clear. + * @example Stop a timeout timer. + * // Print a message after two seconds. + * var timer = Script.setTimeout(function () { + * print("Timer fired"); + * }, 2000); + * + * // Uncomment the following line to stop the timer from firing. + * //Script.clearTimeout(timer); + */ Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + /**jsdoc + * @function Script.print + * @param {string} message + */ Q_INVOKABLE void print(const QString& message); + + /**jsdoc + * Resolve a relative path to an absolute path. + * @function Script.resolvePath + * @param {string} path - The relative path to resolve. + * @returns {string} The absolute path. + */ Q_INVOKABLE QUrl resolvePath(const QString& path) const; + + /**jsdoc + * @function Script.resourcesPath + * @returns {string} + */ Q_INVOKABLE QUrl resourcesPath() const; + + /**jsdoc + * @function Script.beginProfileRange + * @param {string} label + */ Q_INVOKABLE void beginProfileRange(const QString& label) const; + + /**jsdoc + * @function Script.endProfileRange + * @param {string} label + */ Q_INVOKABLE void endProfileRange(const QString& label) const; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Entity Script Related methods + + /**jsdoc + * @function Script.isEntityScriptRunning + * @param {Uuid} entityID + * @returns {boolean} + */ Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING; } QVariant cloneEntityScriptDetails(const EntityItemID& entityID); QFuture getLocalEntityScriptDetails(const EntityItemID& entityID) override; + + /**jsdoc + * @function Script.loadEntityScript + * @param {Uuid} entityID + * @param {string} script + * @param {boolean} forceRedownload + */ Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload); + + /**jsdoc + * @function Script.unloadEntityScript + * @param {Uuid} entityID + * @param {boolean} [shouldRemoveFromMap=false] + */ Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method + + /**jsdoc + * @function Script.unloadAllEntityScripts + */ Q_INVOKABLE void unloadAllEntityScripts(); + + /**jsdoc + * @function Script.callEntityScriptMethod + * @param {Uuid} entityID + * @param {string} methodName + * @param {string[]} parameters + * @param {Uuid} [remoteCallerID=Uuid.NULL] + */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList(), const QUuid& remoteCallerID = QUuid()) override; + + /**jsdoc + * @function Script.callEntityScriptMethod + * @param {Uuid} entityID + * @param {string} methodName + * @param {PointerEvent} event + */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event); + + /**jsdoc + * @function Script.callEntityScriptMethod + * @param {Uuid} entityID + * @param {string} methodName + * @param {Uuid} otherID + * @param {Collision} collision + */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); + /**jsdoc + * @function Script.requestGarbageCollection + */ Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } + /**jsdoc + * @function Script.generateUUID + * @returns {Uuid} + */ Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); } bool isFinished() const { return _isFinished; } // used by Application and ScriptWidget @@ -239,33 +523,170 @@ public: bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; public slots: + + /**jsdoc + * @function Script.callAnimationStateHandler + * @param {function} callback + * @param {object} parameters + * @param {string[]} names + * @param {boolean} useNames + * @param {object} resultHandler + */ void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); + + /**jsdoc + * @function Script.updateMemoryCost + * @param {number} deltaSize + */ void updateMemoryCost(const qint64&); signals: + + /**jsdoc + * @function Script.scriptLoaded + * @param {string} filename + * @returns {Signal} + */ void scriptLoaded(const QString& scriptFilename); + + /**jsdoc + * @function Script.errorLoadingScript + * @param {string} filename + * @returns {Signal} + */ void errorLoadingScript(const QString& scriptFilename); + + /**jsdoc + * Triggered regularly at a system-determined frequency. + * @function Script.update + * @param {number} deltaTime - The time since the last update, in s. + * @returns {Signal} + */ void update(float deltaTime); + + /**jsdoc + * Triggered when the script is ending. + * @function Script.scriptEnding + * @returns {Signal} + * @example Connect to the scriptEnding signal. + * print("Script started"); + * + * Script.scriptEnding.connect(function () { + * print("Script ending"); + * }); + * + * Script.setTimeout(function () { + * print("Stopping script"); + * Script.stop(); + * }, 1000); + */ void scriptEnding(); + + /**jsdoc + * @function Script.finished + * @param {string} filename + * @param {object} engine + * @returns {Signal} + */ void finished(const QString& fileNameString, ScriptEnginePointer); + + /**jsdoc + * @function Script.cleanupMenuItem + * @param {string} menuItem + * @returns {Signal} + */ void cleanupMenuItem(const QString& menuItemString); + + /**jsdoc + * @function Script.printedMessage + * @param {string} message + * @param {string} scriptName + * @returns {Signal} + */ void printedMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function Script.errorMessage + * @param {string} message + * @param {string} scriptName + * @returns {Signal} + */ void errorMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function Script.warningMessage + * @param {string} message + * @param {string} scriptName + * @returns {Signal} + */ void warningMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function Script.infoMessage + * @param {string} message + * @param {string} scriptName + * @returns {Signal} + */ void infoMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function Script.runningStateChanged + * @returns {Signal} + */ void runningStateChanged(); + + /**jsdoc + * @function Script.clearDebugWindow + * @returns {Signal} + */ void clearDebugWindow(); + + /**jsdoc + * @function Script.loadScript + * @param {string} scriptName + * @param {boolean} isUserLoaded + * @returns {Signal} + */ void loadScript(const QString& scriptName, bool isUserLoaded); + + /**jsdoc + * @function Script.reloadScript + * @param {string} scriptName + * @param {boolean} isUserLoaded + * @returns {Signal} + */ void reloadScript(const QString& scriptName, bool isUserLoaded); + + /**jsdoc + * @function Script.doneRunning + * @returns {Signal} + */ void doneRunning(); + /**jsdoc + * @function Script.entityScriptDetailsUpdated + * @returns {Signal} + */ // Emitted when an entity script is added or removed, or when the status of an entity // script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example) void entityScriptDetailsUpdated(); protected: void init(); + + /**jsdoc + * @function Script.executeOnScriptThread + * @param {object} function + * @param {ConnectionType} [type=2] + */ Q_INVOKABLE void executeOnScriptThread(std::function function, const Qt::ConnectionType& type = Qt::QueuedConnection ); + + /**jsdoc + * @function Script._requireResolve + * @param {string} module + * @param {string} [relativeTo=""] + * @returns {string} + */ // note: this is not meant to be called directly, but just to have QMetaObject take care of wiring it up in general; // then inside of init() we just have to do "Script.require.resolve = Script._requireResolve;" Q_INVOKABLE QString _requireResolve(const QString& moduleId, const QString& relativeTo = QString()); @@ -285,6 +706,16 @@ protected: QHash _registeredHandlers; void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs); + + /**jsdoc + * @function Script.entityScriptContentAvailable + * @param {Uuid} entityID + * @param {string} scriptOrURL + * @param {string} contents + * @param {boolean} isURL + * @param {boolean} success + * @param {string} status + */ Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString& status); EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution. diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 871705d74b..a788cd9f0e 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -273,9 +273,9 @@ QVariantList ScriptEngines::getRunning() { } else { displayURLString = displayURL.toDisplayString(QUrl::FormattingOptions(QUrl::FullyEncoded)); } - resultNode.insert("url", displayURLString); // The path contains the exact path/URL of the script, which also is used in the stopScript function. - resultNode.insert("path", normalizeScriptURL(runningScript).toString()); + resultNode.insert("path", displayURLString); + resultNode.insert("url", normalizeScriptURL(runningScript).toString()); resultNode.insert("local", runningScriptURL.isLocalFile()); result.append(resultNode); } @@ -538,7 +538,6 @@ int ScriptEngines::runScriptInitializers(ScriptEnginePointer scriptEngine) { int ii=0; for (auto initializer : _scriptInitializers) { ii++; - qDebug() << "initializer" << ii; initializer(scriptEngine); } return ii; diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index ea07ebe840..1200168420 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -27,6 +27,14 @@ class ScriptEngine; +/**jsdoc + * @namespace ScriptDiscoveryService + * @property {string} debugScriptUrl + * @property {string} defaultScriptsPath + * @property {ScriptsModel} scriptsModel + * @property {ScriptsModelFilter} scriptsModelFilter + */ + class NativeScriptInitializers : public ScriptInitializerMixin { public: bool registerNativeScriptInitializer(NativeScriptInitializer initializer) override; @@ -63,18 +71,64 @@ public: QString getDefaultScriptsLocation() const; + /**jsdoc + * @function ScriptDiscoveryService.loadOneScript + * @param {string} filename + */ Q_INVOKABLE void loadOneScript(const QString& scriptFilename); + + /**jsdoc + * @function ScriptDiscoveryService.loadScript + * @param {string} [filename=""] + * @param {boolean} [isUserLoaded=true] + * @param {boolean} [loadScriptFromEditor=false] + * @param {boolean} [activateMainWindow=false] + * @param {boolean} [reload=false] + * @returns {boolean} + */ Q_INVOKABLE ScriptEnginePointer loadScript(const QUrl& scriptFilename = QString(), bool isUserLoaded = true, bool loadScriptFromEditor = false, bool activateMainWindow = false, bool reload = false); + + /**jsdoc + * @function ScriptDiscoveryService.stopScript + * @param {string} scriptHash + * @param {boolean} [restart=false] + * @returns {boolean} + */ Q_INVOKABLE bool stopScript(const QString& scriptHash, bool restart = false); + + /**jsdoc + * @function ScriptDiscoveryService.reloadAllScripts + */ Q_INVOKABLE void reloadAllScripts(); + + /**jsdoc + * @function ScriptDiscoveryService.stopAllScripts + * @param {boolean} [restart=false] + */ Q_INVOKABLE void stopAllScripts(bool restart = false); + + /**jsdoc + * @function ScriptDiscoveryService.getRunning + * @returns {object[]} + */ Q_INVOKABLE QVariantList getRunning(); + + /**jsdoc + * @function ScriptDiscoveryService.getPublic + * @returns {object[]} + */ Q_INVOKABLE QVariantList getPublic(); + + /**jsdoc + * @function ScriptDiscoveryService.getLocal + * @returns {object[]} + */ Q_INVOKABLE QVariantList getLocal(); + // FIXME: Move to other Q_PROPERTY declarations. Q_PROPERTY(QString defaultScriptsPath READ getDefaultScriptsLocation) void defaultScriptsLocationOverridden(bool overridden) { _defaultScriptsLocationOverridden = overridden; }; @@ -86,25 +140,120 @@ public: void addScriptEngine(ScriptEnginePointer); signals: + + /**jsdoc + * @function ScriptDiscoveryService.scriptCountChanged + * @returns {Signal} + */ void scriptCountChanged(); + + /**jsdoc + * @function ScriptDiscoveryService.scriptsReloading + * @returns {Signal} + */ void scriptsReloading(); + + /**jsdoc + * @function ScriptDiscoveryService.scriptLoadError + * @param {string} filename + * @param {string} error + * @returns {Signal} + */ void scriptLoadError(const QString& filename, const QString& error); + + /**jsdoc + * @function ScriptDiscoveryService.printedMessage + * @param {string} message + * @param {string} engineName + * @returns {Signal} + */ void printedMessage(const QString& message, const QString& engineName); + + /**jsdoc + * @function ScriptDiscoveryService.errorMessage + * @param {string} message + * @param {string} engineName + * @returns {Signal} + */ void errorMessage(const QString& message, const QString& engineName); + + /**jsdoc + * @function ScriptDiscoveryService.warningMessage + * @param {string} message + * @param {string} engineName + * @returns {Signal} + */ void warningMessage(const QString& message, const QString& engineName); + + /**jsdoc + * @function ScriptDiscoveryService.infoMessage + * @param {string} message + * @param {string} engineName + * @returns {Signal} + */ void infoMessage(const QString& message, const QString& engineName); + + /**jsdoc + * @function ScriptDiscoveryService.errorLoadingScript + * @param {string} url + * @returns {Signal} + */ void errorLoadingScript(const QString& url); + + /**jsdoc + * @function ScriptDiscoveryService.clearDebugWindow + * @returns {Signal} + */ void clearDebugWindow(); public slots: + + /**jsdoc + * @function ScriptDiscoveryService.onPrintedMessage + * @param {string} message + * @param {string} scriptName + */ void onPrintedMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function ScriptDiscoveryService.onErrorMessage + * @param {string} message + * @param {string} scriptName + */ void onErrorMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function ScriptDiscoveryService.onWarningMessage + * @param {string} message + * @param {string} scriptName + */ void onWarningMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function ScriptDiscoveryService.onInfoMessage + * @param {string} message + * @param {string} scriptName + */ void onInfoMessage(const QString& message, const QString& scriptName); + + /**jsdoc + * @function ScriptDiscoveryService.onErrorLoadingScript + * @param {string} url + */ void onErrorLoadingScript(const QString& url); + + /**jsdoc + * @function ScriptDiscoveryService.onClearDebugWindow + */ void onClearDebugWindow(); protected slots: + + /**jsdoc + * @function ScriptDiscoveryService.onScriptFinished + * @param {string} filename + * @param {object} engine + */ void onScriptFinished(const QString& fileNameString, ScriptEnginePointer engine); protected: diff --git a/libraries/script-engine/src/ScriptsModel.h b/libraries/script-engine/src/ScriptsModel.h index a3ca554e51..a4ffc192f9 100644 --- a/libraries/script-engine/src/ScriptsModel.h +++ b/libraries/script-engine/src/ScriptsModel.h @@ -63,17 +63,68 @@ public: TreeNodeFolder(const QString& foldername, TreeNodeFolder* parent); }; +/**jsdoc + *

    Provided as a property of {@link ScriptDiscoveryService}.

    + *

    Has properties and functions below in addition to those of + * http://doc.qt.io/qt-5/qabstractitemmodel.html.

    + * @class ScriptsModel + */ class ScriptsModel : public QAbstractItemModel { Q_OBJECT public: ScriptsModel(QObject* parent = NULL); ~ScriptsModel(); + + /**jsdoc + * @function ScriptsModel.index + * @param {number} row + * @param {number} column + * @param {QModelIndex} parent + * @returns {QModelIndex} + */ QModelIndex index(int row, int column, const QModelIndex& parent) const override; + + /**jsdoc + * @function ScriptsModel.parent + * @param {QModelIndex} child + * @returns {QModelIndex} + */ QModelIndex parent(const QModelIndex& child) const override; + + /**jsdoc + * @function ScriptsModel.data + * @param {QModelIndex} index + * @param {number} [role=0] + * returns {string} + */ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + /**jsdoc + * @function ScriptsModel.rowCount + * @param {QmodelIndex} [parent=null] + * @returns {number} + */ int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + /**jsdoc + * @function ScriptsModel.columnCount + * @param {QmodelIndex} [parent=null] + * @returns {number} + */ int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + /**jsdoc + * @function ScriptsModel.getTreeNodeFromIndex + * @param {QmodelIndex} index + * @returns {TreeNodeBase} + */ TreeNodeBase* getTreeNodeFromIndex(const QModelIndex& index) const; + + /**jsdoc + * @function ScriptsModel.getFolderNodes + * @param {TreeNodeFolder} parent + * @returns {TreeNodeBase[]} + */ QList getFolderNodes(TreeNodeFolder* parent) const; enum Role { diff --git a/libraries/script-engine/src/ScriptsModelFilter.h b/libraries/script-engine/src/ScriptsModelFilter.h index 4854665d12..26efde02e8 100644 --- a/libraries/script-engine/src/ScriptsModelFilter.h +++ b/libraries/script-engine/src/ScriptsModelFilter.h @@ -15,6 +15,12 @@ #include "ScriptsModel.h" #include +/**jsdoc + *

    Provided as a property of {@link ScriptDiscoveryService}.

    + *

    Has properties and functions per + * http://doc.qt.io/qt-5/qsortfilterproxymodel.html.

    + * @class ScriptsModelFilter + */ class ScriptsModelFilter : public QSortFilterProxyModel { Q_OBJECT public: diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index a4e741a797..6728c471f6 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -17,8 +17,12 @@ #include /**jsdoc -* @namespace Users -*/ + * @namespace Users + * @property {boolean} canKick - true if the domain server allows the node or avatar to kick (ban) avatars, + * otherwise false. Read-only. + * @property {boolean} requestsDomainListData - true if the avatar requests extra data from the mixers (such as + * positional data of an avatar you've ignored). Read-only. + */ class UsersScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -32,126 +36,151 @@ public: public slots: /**jsdoc - * Ignore another user. - * @function Users.ignore - * @param {nodeID} nodeID The node or session ID of the user you want to ignore. - * @param {bool} enable True for ignored; false for un-ignored. - */ + * Personally ignore another user, making them disappear for you and you disappear for them. + * @function Users.ignore + * @param {Uuid} nodeID The node or session ID of the user you want to ignore. + * @param {boolean} enable True for ignored; false for un-ignored. + */ void ignore(const QUuid& nodeID, bool ignoreEnabled = true); /**jsdoc - * Gets a bool containing whether you have ignored the given Avatar UUID. - * @function Users.getIgnoreStatus - * @param {nodeID} nodeID The node or session ID of the user whose ignore status you want. - */ + * Get whether or not you have ignored the node with the given UUID. + * @function Users.getIgnoreStatus + * @param {Uuid} nodeID The node or session ID of the user whose ignore status you want. + * @returns {boolean} + */ bool getIgnoreStatus(const QUuid& nodeID); /**jsdoc - * Mute another user for you and you only. - * @function Users.personalMute - * @param {nodeID} nodeID The node or session ID of the user you want to mute. - * @param {bool} enable True for enabled; false for disabled. - */ + * Mute another user for you and you only. They won't be able to hear you, and you won't be able to hear them. + * @function Users.personalMute + * @param {Uuid} nodeID The node or session ID of the user you want to mute. + * @param {boolean} muteEnabled True for enabled; false for disabled. + */ void personalMute(const QUuid& nodeID, bool muteEnabled = true); /**jsdoc - * Requests a bool containing whether you have personally muted the given Avatar UUID. - * @function Users.requestPersonalMuteStatus - * @param {nodeID} nodeID The node or session ID of the user whose personal mute status you want. - */ + * Get whether or not you have personally muted the node with the given UUID. + * @function Users.requestPersonalMuteStatus + * @param {Uuid} nodeID The node or session ID of the user whose personal mute status you want. + * @returns {boolean} + */ bool getPersonalMuteStatus(const QUuid& nodeID); /**jsdoc - * Sets an avatar's gain for you and you only. - * Units are Decibels (dB) - * @function Users.setAvatarGain - * @param {nodeID} nodeID The node or session ID of the user whose gain you want to modify, or null to set the master gain. - * @param {float} gain The gain of the avatar you'd like to set. Units are dB. + * Sets an avatar's gain for you and you only. + * Units are Decibels (dB) + * @function Users.setAvatarGain + * @param {Uuid} nodeID The node or session ID of the user whose gain you want to modify, or null to set the master gain. + * @param {number} gain The gain of the avatar you'd like to set. Units are dB. */ void setAvatarGain(const QUuid& nodeID, float gain); /**jsdoc - * Gets an avatar's gain for you and you only. - * @function Users.getAvatarGain - * @param {nodeID} nodeID The node or session ID of the user whose gain you want to get, or null to get the master gain. - * @return {float} gain (in dB) + * Gets an avatar's gain for you and you only. + * @function Users.getAvatarGain + * @param {Uuid} nodeID The node or session ID of the user whose gain you want to get, or null to get the master gain. + * @returns {number} gain (in dB) */ float getAvatarGain(const QUuid& nodeID); /**jsdoc - * Kick another user. - * @function Users.kick - * @param {nodeID} nodeID The node or session ID of the user you want to kick. - */ + * Kick/ban another user. Removes them from the server and prevents them from returning. Bans by either user name (if + * available) or machine fingerprint otherwise. This will only do anything if you're an admin of the domain you're in. + * @function Users.kick + * @param {Uuid} nodeID The node or session ID of the user you want to kick. + */ void kick(const QUuid& nodeID); /**jsdoc - * Mute another user for everyone. - * @function Users.mute - * @param {nodeID} nodeID The node or session ID of the user you want to mute. - */ + * Mutes another user's microphone for everyone. Not permanent; the silenced user can unmute themselves with the UNMUTE + * button in their HUD. This will only do anything if you're an admin of the domain you're in. + * @function Users.mute + * @param {Uuid} nodeID The node or session ID of the user you want to mute. + */ void mute(const QUuid& nodeID); /**jsdoc - * Returns a string containing the username associated with the given Avatar UUID + * Get the user name and machine fingerprint associated with the given UUID. This will only do anything if you're an admin + * of the domain you're in. * @function Users.getUsernameFromID - * @param {nodeID} nodeID The node or session ID of the user whose username you want. + * @param {Uuid} nodeID The node or session ID of the user whose username you want. */ void requestUsernameFromID(const QUuid& nodeID); /**jsdoc - * Returns `true` if the DomainServer will allow this Node/Avatar to make kick - * @function Users.getCanKick - * @return {bool} `true` if the client can kick other users, `false` if not. - */ + * Returns `true` if the DomainServer will allow this Node/Avatar to make kick. + * @function Users.getCanKick + * @returns {boolean} true if the domain server allows the client to kick (ban) other users, otherwise + * false. + */ bool getCanKick(); /**jsdoc - * Toggle the state of the ignore in radius feature - * @function Users.toggleIgnoreRadius - */ + * Toggle the state of the space bubble feature. + * @function Users.toggleIgnoreRadius + */ void toggleIgnoreRadius(); /**jsdoc - * Enables the ignore radius feature. - * @function Users.enableIgnoreRadius - */ + * Enables the space bubble feature. + * @function Users.enableIgnoreRadius + */ void enableIgnoreRadius(); /**jsdoc - * Disables the ignore radius feature. - * @function Users.disableIgnoreRadius - */ + * Disables the space bubble feature. + * @function Users.disableIgnoreRadius + */ void disableIgnoreRadius(); /**jsdoc - * Returns `true` if the ignore in radius feature is enabled - * @function Users.getIgnoreRadiusEnabled - * @return {bool} `true` if the ignore in radius feature is enabled, `false` if not. - */ + * Returns `true` if the space bubble feature is enabled. + * @function Users.getIgnoreRadiusEnabled + * @returns {boolean} true if the space bubble is enabled, otherwise false. + */ bool getIgnoreRadiusEnabled(); signals: + + /**jsdoc + * @function Users.canKickChanged + * @param {boolean} canKick + * @returns {Signal} + */ void canKickChanged(bool canKick); + + /**jsdoc + * @function Users.ignoreRadiusEnabledChanged + * @param {boolean} isEnabled + * @returns {Signal} + */ void ignoreRadiusEnabledChanged(bool isEnabled); /**jsdoc - * Notifies scripts that another user has entered the ignore radius - * @function Users.enteredIgnoreRadius - */ + * Notifies scripts that another user has entered the ignore radius. + * @function Users.enteredIgnoreRadius + * @returns {Signal} + */ void enteredIgnoreRadius(); /**jsdoc - * Notifies scripts of the username and machine fingerprint associated with a UUID. - * Username and machineFingerprint will be their default constructor output if the requesting user isn't an admin. - * @function Users.usernameFromIDReply + * Notifies scripts of the user name and machine fingerprint associated with a UUID. + * Username and machineFingerprint will be their default constructor output if the requesting user isn't an admin. + * @function Users.usernameFromIDReply + * @param {Uuid} nodeID + * @param {string} userName + * @param {string} machineFingerprint + * @param {boolean} isAdmin + * @returns {Signal} */ void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint, bool isAdmin); /**jsdoc - * Notifies scripts that a user has disconnected from the domain + * Notifies scripts that a user has disconnected from the domain. * @function Users.avatarDisconnected - * @param {nodeID} NodeID The session ID of the avatar that has disconnected + * @param {Uuid} nodeID The session ID of the avatar that has disconnected. + * @returns {Signal} */ void avatarDisconnected(const QUuid& nodeID); diff --git a/libraries/shared/src/ColorUtils.h b/libraries/shared/src/ColorUtils.h index fd0bbdd8ab..e113449db3 100644 --- a/libraries/shared/src/ColorUtils.h +++ b/libraries/shared/src/ColorUtils.h @@ -102,7 +102,7 @@ inline float ColorUtils::tosRGBFloat(const float &linear) { } else if (0 < linear && linear < SRGB_ELBOW_INV) { sRGBValue = 12.92f * linear; } else if (SRGB_ELBOW_INV <= linear && linear < 1) { - sRGBValue = 1.055f * powf(linear, 0.41666f - 0.055f); + sRGBValue = 1.055f * powf(linear, 0.41666f) - 0.055f; } else { sRGBValue = 1.0f; } diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index b1ceab4149..c7ad4a790d 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -213,7 +213,7 @@ void setOctalCodeSectionValue(unsigned char* octalCode, int section, char sectio int byteForSection = (BITS_IN_OCTAL * section / BITS_IN_BYTE); unsigned char* byteAt = octalCode + 1 + byteForSection; char bitInByte = (BITS_IN_OCTAL * section) % BITS_IN_BYTE; - char shiftBy = BITS_IN_BYTE - bitInByte - BITS_IN_OCTAL; + int8_t shiftBy = BITS_IN_BYTE - bitInByte - BITS_IN_OCTAL; const unsigned char UNSHIFTED_MASK = 0x07; unsigned char shiftedMask; unsigned char shiftedValue; diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index a243a6d58d..76d61fe3f6 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -198,6 +198,7 @@ class IntPreference : public TypedPreference { Q_PROPERTY(float min READ getMin CONSTANT) Q_PROPERTY(float max READ getMax CONSTANT) Q_PROPERTY(float step READ getStep CONSTANT) + Q_PROPERTY(int decimals READ getDecimals CONSTANT) public: IntPreference(const QString& category, const QString& name, Getter getter, Setter setter) @@ -212,6 +213,9 @@ public: float getStep() const { return _step; } void setStep(float step) { _step = step; }; + int getDecimals() const { return _decimals; } + void setDecimals(int decimals) { _decimals = decimals; }; + signals: void valueChanged(); @@ -221,6 +225,7 @@ protected: int _min { std::numeric_limits::min() }; int _max { std::numeric_limits::max() }; int _step { 1 }; + int _decimals { 0 }; }; class StringPreference : public TypedPreference { diff --git a/libraries/shared/src/Trace.h b/libraries/shared/src/Trace.h index 93e2c6c4c2..1e1326968f 100644 --- a/libraries/shared/src/Trace.h +++ b/libraries/shared/src/Trace.h @@ -102,6 +102,9 @@ private: }; inline void traceEvent(const QLoggingCategory& category, const QString& name, EventType type, const QString& id = "", const QVariantMap& args = {}, const QVariantMap& extra = {}) { + if (!DependencyManager::isSet()) { + return; + } const auto& tracer = DependencyManager::get(); if (tracer) { tracer->traceEvent(category, name, type, id, args, extra); diff --git a/libraries/shared/src/shared/MiniPromises.cpp b/libraries/shared/src/shared/MiniPromises.cpp index bb78852c29..21a3f44d50 100644 --- a/libraries/shared/src/shared/MiniPromises.cpp +++ b/libraries/shared/src/shared/MiniPromises.cpp @@ -22,6 +22,5 @@ namespace { } void MiniPromise::registerMetaTypes(QObject* engine) { auto scriptEngine = qobject_cast(engine); - qDebug() << "----------------------- MiniPromise::registerMetaTypes ------------" << scriptEngine; qScriptRegisterMetaType(scriptEngine, promiseToScriptValue, promiseFromScriptValue); } diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp index 8fe1454242..b983213e4a 100644 --- a/libraries/shared/src/shared/Storage.cpp +++ b/libraries/shared/src/shared/Storage.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include "StorageLogging.h" Q_LOGGING_CATEGORY(storagelogging, "hifi.core.storage") @@ -102,4 +102,4 @@ FileStorage::~FileStorage() { if (_file.isOpen()) { _file.close(); } -} \ No newline at end of file +} diff --git a/libraries/shared/src/shared/StorageLogging.h b/libraries/shared/src/shared/StorageLogging.h new file mode 100644 index 0000000000..33d89411bd --- /dev/null +++ b/libraries/shared/src/shared/StorageLogging.h @@ -0,0 +1,18 @@ +// +// StorageLogging.h +// +// Created by Seth Alves on 2018-4-20 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_StorageLogging_h +#define hifi_StorageLogging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(storagelogging) + +#endif // hifi_StorageLogging_h diff --git a/libraries/trackers/src/trackers/FaceTracker.h b/libraries/trackers/src/trackers/FaceTracker.h index 58d5c5e574..47fbf72616 100644 --- a/libraries/trackers/src/trackers/FaceTracker.h +++ b/libraries/trackers/src/trackers/FaceTracker.h @@ -18,6 +18,7 @@ #include /// Base class for face trackers (DDE, BinaryVR). + class FaceTracker : public QObject { Q_OBJECT @@ -58,11 +59,27 @@ public: QVector& coefficients); signals: + + /**jsdoc + * @function FaceTracker.muteToggled + * @returns {Signal} + */ void muteToggled(); public slots: + + // No JSDoc here because it's overridden in DdeFaceTracker. virtual void setEnabled(bool enabled) = 0; + + /**jsdoc + * @function FaceTracker.toggleMute + */ void toggleMute(); + + /**jsdoc + * @function FaceTracker.getMuted + * @returns {boolean} + */ bool getMuted() { return _isMuted; } protected: diff --git a/libraries/ui/src/ui/Logging.h b/libraries/ui/src/ui/Logging.h index 6d31b0e86a..dd14268dba 100644 --- a/libraries/ui/src/ui/Logging.h +++ b/libraries/ui/src/ui/Logging.h @@ -6,8 +6,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_Controllers_Logging_h -#define hifi_Controllers_Logging_h +#ifndef hifi_UI_Logging_h +#define hifi_UI_Logging_h #include diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 43b573a169..48e778c063 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -102,7 +102,7 @@ class AudioHandler : public QObject, QRunnable { public: AudioHandler(OffscreenQmlSurface* surface, const QString& deviceName, QObject* parent = nullptr); - virtual ~AudioHandler() { qDebug() << "Audio Handler Destroyed"; } + virtual ~AudioHandler() { } void run() override; @@ -115,6 +115,7 @@ private: class UrlHandler : public QObject { Q_OBJECT public: + UrlHandler(QObject* parent = nullptr) : QObject(parent) {} Q_INVOKABLE bool canHandleUrl(const QString& url) { static auto handler = dynamic_cast(qApp); return handler && handler->canAcceptURL(url); @@ -223,6 +224,17 @@ void AudioHandler::run() { qDebug() << "QML Audio changed to " << _newTargetDevice; } +OffscreenQmlSurface::~OffscreenQmlSurface() { + clearFocusItem(); +} + +void OffscreenQmlSurface::clearFocusItem() { + if (_currentFocusItem) { + disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); + } + _currentFocusItem = nullptr; +} + void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { Parent::initializeEngine(engine); new QQmlFileSelector(engine); @@ -246,7 +258,7 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { auto rootContext = engine->rootContext(); rootContext->setContextProperty("GL", ::getGLContextData()); - rootContext->setContextProperty("urlHandler", new UrlHandler()); + rootContext->setContextProperty("urlHandler", new UrlHandler(rootContext)); rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); rootContext->setContextProperty("ApplicationInterface", qApp); auto javaScriptToInject = getEventBridgeJavascript(); @@ -545,17 +557,15 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT } void OffscreenQmlSurface::focusDestroyed(QObject* obj) { - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); - } - _currentFocusItem = nullptr; + clearFocusItem(); } void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { + clearFocusItem(); + QQuickItem* item = static_cast(object); if (!item) { setFocusText(false); - _currentFocusItem = nullptr; return; } @@ -563,10 +573,6 @@ void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { qApp->sendEvent(object, &query); setFocusText(query.value(Qt::ImEnabled).toBool()); - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, 0); - } - // Raise and lower keyboard for QML text fields. // HTML text fields are handled in emitWebEvent() methods - testing READ_ONLY_PROPERTY prevents action for HTML files. const char* READ_ONLY_PROPERTY = "readOnly"; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 9fa86f12a3..b95a8f117d 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -22,7 +22,8 @@ class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { Q_OBJECT Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: - + ~OffscreenQmlSurface(); + static void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback); static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; @@ -58,6 +59,7 @@ public slots: void sendToQml(const QVariant& message); protected: + void clearFocusItem(); void setFocusText(bool newFocusText); void initializeEngine(QQmlEngine* engine) override; void onRootContextCreated(QQmlContext* qmlContext) override; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index a8c8ddd9c8..bab15fc7b6 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -44,6 +44,23 @@ class OffscreenQmlSurface; class TabletScriptingInterface : public QObject, public Dependency { Q_OBJECT public: + + /**jsdoc + * + * + * + * + * + * + * + * + * + * + * + * + *
    ValueDescription
    0Button click.
    1Button hover.
    2Tablet open.
    3Tablet hands in.
    4Tablet hands out.
    5Last.
    + * @typedef {number} Tablet.AudioEvents + */ enum TabletAudioEvents { ButtonClick, ButtonHover, TabletOpen, TabletHandsIn, TabletHandsOut, Last}; Q_ENUM(TabletAudioEvents) @@ -58,14 +75,19 @@ public: void setToolbarScriptingInterface(ToolbarScriptingInterface* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; } /**jsdoc - * Creates or retruns a new TabletProxy and returns it. + * Creates or returns a new TabletProxy and returns it. * @function Tablet.getTablet - * @param name {String} tablet name - * @return {TabletProxy} tablet instance + * @param {string} name - Tablet name. + * @returns {TabletProxy} Tablet instance. */ Q_INVOKABLE TabletProxy* getTablet(const QString& tabletId); void preloadSounds(); + + /**jsdoc + * @function Tablet.playSound + * @param {Tablet.AudioEvents} sound + */ Q_INVOKABLE void playSound(TabletAudioEvents aEvent); void setToolbarMode(bool toolbarMode); @@ -79,8 +101,8 @@ public: QObject* getFlags(); signals: /**jsdoc - * Signaled when a tablet message or dialog is created - * @function TabletProxy#tabletNotification + * Triggered when a tablet message or dialog is created. + * @function Tablet.tabletNotification * @returns {Signal} */ void tabletNotification(); @@ -98,6 +120,9 @@ protected: bool _toolbarMode { false }; }; +/**jsdoc + * @typedef {object} TabletProxy#ButtonList + */ class TabletButtonListModel : public QAbstractListModel { Q_OBJECT @@ -151,9 +176,12 @@ Q_DECLARE_METATYPE(TabletButtonsProxyModel*); /**jsdoc * @class TabletProxy - * @property name {string} READ_ONLY: name of this tablet - * @property toolbarMode {bool} - used to transition this tablet into and out of toolbar mode. + * @property {string} name - Name of this tablet. Read-only. + * @property {boolean} toolbarMode - Used to transition this tablet into and out of toolbar mode. * When tablet is in toolbar mode, all its buttons will appear in a floating toolbar. + * @property {boolean} landscape + * @property {boolean} tabletShown Read-only. + * @property {TabletProxy#ButtonList} buttons Read-only. */ class TabletProxy : public QObject { Q_OBJECT @@ -172,88 +200,136 @@ public: bool getToolbarMode() const { return _toolbarMode; } void setToolbarMode(bool toolbarMode); - + /**jsdoc + * @function TabletProxy#gotoMenuScreen + * @param {string} [submenu=""] + */ Q_INVOKABLE void gotoMenuScreen(const QString& submenu = ""); - Q_INVOKABLE void initialScreen(const QVariant& url); - /**jsdoc - * transition to the home screen + * @function TabletProxy#initialScreen + * @param {string} url + */ + Q_INVOKABLE void initialScreen(const QVariant& url); + + /**jsdoc + * Transition to the home screen. * @function TabletProxy#gotoHomeScreen */ Q_INVOKABLE void gotoHomeScreen(); /**jsdoc - * show the specified web url on the tablet. + * Show the specified Web url on the tablet. * @function TabletProxy#gotoWebScreen - * @param url {string} url of web page. - * @param [injectedJavaScriptUrl] {string} optional url to an additional JS script to inject into the web page. + * @param {string} url - URL of web page. + * @param {string} [injectedJavaScriptUrl=""] - URL to an additional JS script to inject into the web page. + * @param {boolean} [loadOtherBase=false] */ Q_INVOKABLE void gotoWebScreen(const QString& url); Q_INVOKABLE void gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl, bool loadOtherBase = false); + /**jsdoc + * @function TabletProxy#loadQMLSource + * @param {string} path + * @param {boolean} [resizable=false] + */ Q_INVOKABLE void loadQMLSource(const QVariant& path, bool resizable = false); // FIXME: This currently relies on a script initializing the tablet (hence the bool denoting success); // it should be initialized internally so it cannot fail - Q_INVOKABLE bool pushOntoStack(const QVariant& path); - Q_INVOKABLE void popFromStack(); - - Q_INVOKABLE void loadQMLOnTop(const QVariant& path); - Q_INVOKABLE void loadWebScreenOnTop(const QVariant& url); - Q_INVOKABLE void loadWebScreenOnTop(const QVariant& url, const QString& injectedJavaScriptUrl); - Q_INVOKABLE void returnToPreviousApp(); - - /**jsdoc - * Check if the tablet has a message dialog open + * @function TabletProxy#pushOntoStack + * @param {string} path + * @returns {boolean} + */ + Q_INVOKABLE bool pushOntoStack(const QVariant& path); + + /**jsdoc + * @function TabletProxy#popFromStack + */ + Q_INVOKABLE void popFromStack(); + + /**jsdoc + * @function TabletProxy#loadQMLOnTop + * @param {string} path + */ + Q_INVOKABLE void loadQMLOnTop(const QVariant& path); + + /**jsdoc + * @function TabletProxy#loadWebScreenOnTop + * @param {string} path + * @param {string} [injectedJavaScriptURL=""] + */ + Q_INVOKABLE void loadWebScreenOnTop(const QVariant& url); + Q_INVOKABLE void loadWebScreenOnTop(const QVariant& url, const QString& injectedJavaScriptUrl); + + /**jsdoc + * @function TabletProxy#returnToPreviousApp + */ + Q_INVOKABLE void returnToPreviousApp(); + + /**jsdoc + * Check if the tablet has a message dialog open. * @function TabletProxy#isMessageDialogOpen + * @returns {boolean} */ Q_INVOKABLE bool isMessageDialogOpen(); /**jsdoc * Creates a new button, adds it to this and returns it. * @function TabletProxy#addButton - * @param properties {Object} button properties UI_TABLET_HACK: enumerate these when we figure out what they should be! + * @param {object} properties - Button properties. * @returns {TabletButtonProxy} */ + //FIXME: UI_TABLET_HACK: enumerate the button properties when we figure out what they should be! Q_INVOKABLE TabletButtonProxy* addButton(const QVariant& properties); /**jsdoc - * removes button from the tablet - * @function TabletProxy.removeButton - * @param tabletButtonProxy {TabletButtonProxy} button to be removed + * Removes a button from the tablet. + * @function TabletProxy#removeButton + * @param {TabletButtonProxy} button - The button to be removed */ Q_INVOKABLE void removeButton(TabletButtonProxy* tabletButtonProxy); /**jsdoc - * Used to send an event to the html/js embedded in the tablet + * Used to send an event to the HTML/JavaScript embedded in the tablet. * @function TabletProxy#emitScriptEvent - * @param msg {object|string} + * @param {object|string} message */ Q_INVOKABLE void emitScriptEvent(const QVariant& msg); /**jsdoc - * Used to send an event to the qml embedded in the tablet + * Used to send an event to the QML embedded in the tablet. * @function TabletProxy#sendToQml - * @param msg {object|string} + * @param {object|string} message */ Q_INVOKABLE void sendToQml(const QVariant& msg); /**jsdoc - * Check if the tablet is on the homescreen - * @function TabletProxy#onHomeScreen() + * Check if the tablet is on the home screen. + * @function TabletProxy#onHomeScreen + * @returns {boolean} */ Q_INVOKABLE bool onHomeScreen(); /**jsdoc - * set tablet into our out of landscape mode + * Set tablet into or out of landscape mode. * @function TabletProxy#setLandscape - * @param landscape {bool} true for landscape, false for portrait + * @param {boolean} landscape - true for landscape, false for portrait. */ Q_INVOKABLE void setLandscape(bool landscape) { _landscape = landscape; } + + /**jsdoc + * @function TabletProxy#getLandscape + * @returns {boolean} + */ Q_INVOKABLE bool getLandscape() { return _landscape; } + /**jsdoc + * @function TabletProxy#isPathLoaded + * @param {string} path + * @returns {boolean} + */ Q_INVOKABLE bool isPathLoaded(const QVariant& path); QQuickItem* getTabletRoot() const { return _qmlTabletRoot; } @@ -268,17 +344,17 @@ public: signals: /**jsdoc - * Signaled when this tablet receives an event from the html/js embedded in the tablet + * Signaled when this tablet receives an event from the html/js embedded in the tablet. * @function TabletProxy#webEventReceived - * @param msg {object|string} + * @param {object|string} message * @returns {Signal} */ void webEventReceived(QVariant msg); /**jsdoc - * Signaled when this tablet receives an event from the qml embedded in the tablet + * Signaled when this tablet receives an event from the qml embedded in the tablet. * @function TabletProxy#fromQml - * @param msg {object|string} + * @param {object|string} message * @returns {Signal} */ void fromQml(QVariant msg); @@ -286,18 +362,21 @@ signals: /**jsdoc * Signaled when this tablet screen changes. * @function TabletProxy#screenChanged - * @param type {string} - "Home", "Web", "Menu", "QML", "Closed" - * @param url {string} - only valid for Web and QML. + * @param type {string} - "Home", "Web", "Menu", "QML", "Closed". + * @param url {string} - Only valid for Web and QML. */ void screenChanged(QVariant type, QVariant url); /**jsdoc - * Signaled when the tablet becomes visible or becomes invisible - * @function TabletProxy#isTabletShownChanged - * @returns {Signal} - */ + * Signaled when the tablet becomes visible or becomes invisible. + * @function TabletProxy#isTabletShownChanged + * @returns {Signal} + */ void tabletShownChanged(); + /**jsdoc + * @function TabletProxy#toolbarModeChanged + */ void toolbarModeChanged(); protected slots: @@ -331,7 +410,8 @@ Q_DECLARE_METATYPE(TabletProxy*); /**jsdoc * @class TabletButtonProxy - * @property uuid {QUuid} READ_ONLY: uniquely identifies this button + * @property {Uuid} uuid - Uniquely identifies this button. Read-only. + * @property {TabletButtonProxy.ButtonProperties} properties */ class TabletButtonProxy : public QObject { Q_OBJECT @@ -344,48 +424,55 @@ public: QUuid getUuid() const { return _uuid; } /**jsdoc - * Returns the current value of this button's properties + * Returns the current value of this button's properties. * @function TabletButtonProxy#getProperties - * @returns {ButtonProperties} + * @returns {TabletButtonProxy.ButtonProperties} */ Q_INVOKABLE QVariantMap getProperties(); /**jsdoc - * Replace the values of some of this button's properties + * Replace the values of some of this button's properties. * @function TabletButtonProxy#editProperties - * @param {ButtonProperties} properties - set of properties to change + * @param {TabletButtonProxy.ButtonProperties} properties - Set of properties to change. */ Q_INVOKABLE void editProperties(const QVariantMap& properties); signals: /**jsdoc - * Signaled when this button has been clicked on by the user. + * Triggered when this button has been clicked on by the user. * @function TabletButtonProxy#clicked * @returns {Signal} */ void clicked(); + + /**jsdoc + * @function TabletButtonProxy#propertiesChanged + * @returns {Signal} + */ void propertiesChanged(); protected: QUuid _uuid; int _stableOrder; + + /**jsdoc + * @typedef TabletButtonProxy.ButtonProperties + * @property {string} icon - URL to button icon. (50 x 50) + * @property {string} hoverIcon - URL to button icon, displayed during mouse hover. (50 x 50) + * @property {string} activeHoverIcon - URL to button icon used when button is active, and during mouse hover. (50 x 50) + * @property {string} activeIcon - URL to button icon used when button is active. (50 x 50) + * @property {string} text - Button caption. + * @property {string} hoverText - Button caption when button is not-active but during mouse hover. + * @property {string} activeText - Button caption when button is active. + * @property {string} activeHoverText - Button caption when button is active and during mouse hover. + * @property {boolean} isActive - true when button is active. + * @property {number} sortOrder - Determines sort order on tablet. lower numbers will appear before larger numbers. + * Default is 100. + */ + // FIXME: There are additional properties. QVariantMap _properties; }; Q_DECLARE_METATYPE(TabletButtonProxy*); -/**jsdoc - * @typedef TabletButtonProxy.ButtonProperties - * @property {string} icon - url to button icon. (50 x 50) - * @property {string} hoverIcon - url to button icon, displayed during mouse hover. (50 x 50) - * @property {string} activeHoverIcon - url to button icon used when button is active, and during mouse hover. (50 x 50) - * @property {string} activeIcon - url to button icon used when button is active. (50 x 50) - * @property {string} text - button caption - * @property {string} hoverText - button caption when button is not-active but during mouse hover. - * @property {string} activeText - button caption when button is active - * @property {string} activeHoverText - button caption when button is active and during mouse hover. - * @property {string} isActive - true when button is active. - * @property {number} sortOrder - determines sort order on tablet. lower numbers will appear before larger numbers. default is 100 - */ - #endif // hifi_TabletScriptingInterface_h diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 2949e72c74..714cb91b3f 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -36,7 +36,6 @@ Q_DECLARE_LOGGING_CATEGORY(displayplugins) -const char* OpenVrDisplayPlugin::NAME { "OpenVR (Vive)" }; const char* StandingHMDSensorMode { "Standing HMD Sensor Mode" }; // this probably shouldn't be hardcoded here const char* OpenVrThreadedSubmit { "OpenVR Threaded Submit" }; // this probably shouldn't be hardcoded here @@ -410,6 +409,15 @@ void OpenVrDisplayPlugin::init() { emit deviceConnected(getName()); } +const QString OpenVrDisplayPlugin::getName() const { + std::string headsetName = getOpenVrDeviceName(); + if (headsetName == "HTC") { + headsetName += " Vive"; + } + + return QString::fromStdString(headsetName); +} + bool OpenVrDisplayPlugin::internalActivate() { if (!_system) { _system = acquireOpenVrSystem(); @@ -444,7 +452,6 @@ bool OpenVrDisplayPlugin::internalActivate() { _openVrDisplayActive = true; _container->setIsOptionChecked(StandingHMDSensorMode, true); - _system->GetRecommendedRenderTargetSize(&_renderTargetSize.x, &_renderTargetSize.y); // Recommended render target size is per-eye, so double the X size for // left + right eyes diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index f681852cd5..15a434341d 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -36,7 +36,7 @@ class OpenVrDisplayPlugin : public HmdDisplayPlugin { using Parent = HmdDisplayPlugin; public: bool isSupported() const override; - const QString getName() const override { return NAME; } + const QString getName() const override; glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; @@ -78,7 +78,6 @@ private: vr::IVRSystem* _system { nullptr }; std::atomic _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; std::atomic _keyboardSupressionCount{ 0 }; - static const char* NAME; vr::HmdMatrix34_t _lastGoodHMDPose; mat4 _sensorResetMat; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index c8a0cb5f8b..9a57413f95 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -66,6 +66,22 @@ bool oculusViaOpenVR() { return enableDebugOpenVR && isOculusPresent() && vr::VR_IsHmdPresent(); } +std::string getOpenVrDeviceName() { + auto system = acquireOpenVrSystem(); + std::string trackingSystemName = ""; + if (system) { + uint32_t HmdTrackingIndex = 0; + uint32_t bufferLength = system->GetStringTrackedDeviceProperty(HmdTrackingIndex, vr::Prop_TrackingSystemName_String, NULL, 0, NULL); + if (bufferLength > 0) { + char* stringBuffer = new char[bufferLength]; + system->GetStringTrackedDeviceProperty(HmdTrackingIndex, vr::Prop_ManufacturerName_String, stringBuffer, bufferLength, NULL); + trackingSystemName = stringBuffer; + delete[] stringBuffer; + } + } + return trackingSystemName; +} + bool openVrSupported() { static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index c54f2326c2..833e5ba65d 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -14,6 +14,7 @@ #include #include +#include bool oculusViaOpenVR(); // is the user using Oculus via OpenVR bool openVrSupported(); @@ -26,6 +27,7 @@ void enableOpenVrKeyboard(PluginContainer* container); void disableOpenVrKeyboard(); bool isOpenVrKeyboardShown(); QString getVrSettingString(const char* section, const char* setting); +std::string getOpenVrDeviceName(); template diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 71ded5f32d..96888a6e8b 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -11,6 +11,7 @@ #include "ViveControllerManager.h" #include +#include #include #include @@ -339,6 +340,12 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle _validTrackedObjects.clear(); _trackedControllers = 0; + if (_headsetName == "") { + _headsetName = getOpenVrDeviceName(); + if (_headsetName == "HTC") { + _headsetName += " Vive"; + } + } // While the keyboard is open, we defer strictly to the keyboard values if (isOpenVrKeyboardShown()) { _axisStateMap.clear(); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 0dd13d95cb..f3631ece9d 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -52,6 +52,8 @@ public: bool activate() override; void deactivate() override; + QString getDeviceName() { return QString::fromStdString(_inputDevice->_headsetName); } + void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; @@ -161,6 +163,7 @@ private: HandConfig _handConfig { HandConfig::HandController }; FilteredStick _filteredLeftStick; FilteredStick _filteredRightStick; + std::string _headsetName {""}; std::vector _validTrackedObjects; std::map _pucksPostOffset; diff --git a/scripts/developer/utilities/render/antialiasing.qml b/scripts/developer/utilities/render/antialiasing.qml index e8034c48bd..8bdd5c6a0d 100644 --- a/scripts/developer/utilities/render/antialiasing.qml +++ b/scripts/developer/utilities/render/antialiasing.qml @@ -98,6 +98,7 @@ Rectangle { property: "covarianceGamma" max: 1.5 min: 0.5 + height: 38 } Separator {} HifiControls.CheckBox { @@ -114,6 +115,7 @@ Rectangle { property: "blend" max: 1.0 min: 0.0 + height: 38 } ConfigSlider { @@ -162,6 +164,7 @@ Rectangle { property: "debugShowVelocityThreshold" max: 50 min: 0.0 + height: 38 } ConfigSlider { label: qsTr("Debug Orb Zoom") @@ -170,6 +173,7 @@ Rectangle { property: "debugOrbZoom" max: 32.0 min: 1.0 + height: 38 } } } diff --git a/scripts/developer/utilities/render/bloom.qml b/scripts/developer/utilities/render/bloom.qml index 66e92e0eff..52090348d9 100644 --- a/scripts/developer/utilities/render/bloom.qml +++ b/scripts/developer/utilities/render/bloom.qml @@ -93,9 +93,10 @@ Item { integral: false config: root.config property: "intensity" - max: 5.0 + max: 1.0 min: 0.0 width: 280 + height:38 } ConfigSlider { label: "Size" @@ -105,6 +106,7 @@ Item { max: 1.0 min: 0.0 width: 280 + height:38 } ConfigSlider { label: "Threshold" @@ -114,6 +116,7 @@ Item { max: 2.0 min: 0.0 width: 280 + height:38 } } } diff --git a/scripts/developer/utilities/render/debugBloom.js b/scripts/developer/utilities/render/debugBloom.js index 2328d524cf..3a508d351c 100644 --- a/scripts/developer/utilities/render/debugBloom.js +++ b/scripts/developer/utilities/render/debugBloom.js @@ -15,6 +15,6 @@ var window = new OverlayWindow({ title: 'Bloom', source: qml, width: 285, - height: 170, + height: 210, }); window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 9403a824e3..aea752c565 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -21,40 +21,16 @@ // BEGIN AVATAR SELECTOR LOGIC - var UNSELECTED_TEXTURES = { - "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") - }; - var SELECTED_TEXTURES = { - "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png") - }; - var HOVER_TEXTURES = { - "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png") - }; - var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; - var conserveResources = true; var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. - function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with. + function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. overlays[key] = this; - if (hasModel) { - var modelKey = key + "-m"; - this.model = new ExtendedOverlay(modelKey, "model", { - url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"), - textures: textures(selected), - ignoreRayIntersection: true - }, false, false); - } else { - this.model = undefined; - } this.key = key; - this.selected = selected || false; // not undefined + this.selected = false; this.hovering = false; this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... } @@ -76,10 +52,6 @@ } return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; } - - function textures(selected, hovering) { - return hovering ? HOVER_TEXTURES : selected ? SELECTED_TEXTURES : UNSELECTED_TEXTURES; - } // so we don't have to traverse the overlays to get the last one var lastHoveringId = 0; ExtendedOverlay.prototype.hover = function (hovering) { @@ -91,9 +63,6 @@ lastHoveringId = 0; } this.editOverlay({ color: color(this.selected, hovering) }); - if (this.model) { - this.model.editOverlay({ textures: textures(this.selected, hovering) }); - } if (hovering) { // un-hover the last hovering overlay if (lastHoveringId && lastHoveringId !== this.key) { @@ -108,15 +77,12 @@ } this.editOverlay({ color: color(selected, this.hovering) }); - if (this.model) { - this.model.editOverlay({ textures: textures(selected) }); - } this.selected = selected; }; // Class methods: - var selectedIds = []; + var selectedId = false; ExtendedOverlay.isSelected = function (id) { - return -1 !== selectedIds.indexOf(id); + return selectedId === id; }; ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier return overlays[key]; @@ -153,51 +119,14 @@ }); }; - function HighlightedEntity(id, entityProperties) { - this.id = id; - this.overlay = Overlays.addOverlay('cube', { - position: entityProperties.position, - rotation: entityProperties.rotation, - dimensions: entityProperties.dimensions, - solid: false, - color: { - red: 0xF3, - green: 0x91, - blue: 0x29 - }, - ignoreRayIntersection: true, - drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene. - }); - HighlightedEntity.overlays.push(this); - } - HighlightedEntity.overlays = []; - HighlightedEntity.clearOverlays = function clearHighlightedEntities() { - HighlightedEntity.overlays.forEach(function (highlighted) { - Overlays.deleteOverlay(highlighted.overlay); - }); - HighlightedEntity.overlays = []; - }; - HighlightedEntity.updateOverlays = function updateHighlightedEntities() { - HighlightedEntity.overlays.forEach(function (highlighted) { - var properties = Entities.getEntityProperties(highlighted.id, ['position', 'rotation', 'dimensions']); - Overlays.editOverlay(highlighted.overlay, { - position: properties.position, - rotation: properties.rotation, - dimensions: properties.dimensions - }); - }); - }; - - function addAvatarNode(id) { - var selected = ExtendedOverlay.isSelected(id); return new ExtendedOverlay(id, "sphere", { drawInFront: true, solid: true, alpha: 0.8, - color: color(selected, false), + color: color(false, false), ignoreRayIntersection: false - }, selected, !conserveResources); + }); } var pingPong = true; @@ -236,14 +165,6 @@ position: target, dimensions: 0.032 * distance }); - if (overlay.model) { - overlay.model.ping = pingPong; - overlay.model.editOverlay({ - position: target, - scale: 0.2 * distance, // constant apparent size - rotation: Camera.orientation - }); - } }); pingPong = !pingPong; ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) @@ -251,13 +172,10 @@ overlay.deleteOverlay(); } }); - // We could re-populateNearbyUserList if anything added or removed, but not for now. - HighlightedEntity.updateOverlays(); } function removeOverlays() { - selectedIds = []; + selectedId = false; lastHoveringId = 0; - HighlightedEntity.clearOverlays(); ExtendedOverlay.some(function (overlay) { overlay.deleteOverlay(); }); @@ -267,7 +185,7 @@ // Clicks. // function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { - if (selectedIds[0] === id) { + if (selectedId === id) { var message = { method: 'updateSelectedRecipientUsername', userName: username === "" ? "unknown username" : username @@ -279,13 +197,13 @@ ExtendedOverlay.applyPickRay(pickRay, function (overlay) { var nextSelectedStatus = !overlay.selected; var avatarId = overlay.key; - selectedIds = nextSelectedStatus ? [avatarId] : []; + selectedId = nextSelectedStatus ? avatarId : false; if (nextSelectedStatus) { Users.requestUsernameFromID(avatarId); } var message = { method: 'selectRecipient', - id: [avatarId], + id: avatarId, isSelected: nextSelectedStatus, displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', userName: '' @@ -298,24 +216,6 @@ overlay.select(selected); }); - HighlightedEntity.clearOverlays(); - if (selectedIds.length) { - Entities.findEntitiesInFrustum(Camera.frustum).forEach(function (id) { - // Because lastEditedBy is per session, the vast majority of entities won't match, - // so it would probably be worth reducing marshalling costs by asking for just we need. - // However, providing property name(s) is advisory and some additional properties are - // included anyway. As it turns out, asking for 'lastEditedBy' gives 'position', 'rotation', - // and 'dimensions', too, so we might as well make use of them instead of making a second - // getEntityProperties call. - // It would be nice if we could harden this against future changes by specifying all - // and only these four in an array, but see - // https://highfidelity.fogbugz.com/f/cases/2728/Entities-getEntityProperties-id-lastEditedBy-name-lastEditedBy-doesn-t-work - var properties = Entities.getEntityProperties(id, 'lastEditedBy'); - if (ExtendedOverlay.isSelected(properties.lastEditedBy)) { - new HighlightedEntity(id, properties); - } - }); - } return true; }); } @@ -657,12 +557,14 @@ } if (onWalletScreen) { + if (!isWired) { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + } isWired = true; - Users.usernameFromIDReply.connect(usernameFromIDReply); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); } else { off(); } diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 10bd5d28d3..1fce772ec8 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -176,7 +176,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var TRIGGER_OFF_VALUE = 0.1; var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab var BUMPER_ON_VALUE = 0.5; - + + var EMPTY_PARENT_ID = "{00000000-0000-0000-0000-000000000000}"; + + var UNEQUIP_KEY = "u"; function getWearableData(props) { var wearable = {}; @@ -270,6 +273,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.shouldSendStart = false; this.equipedWithSecondary = false; this.handHasBeenRightsideUp = false; + this.mouseEquip = false; this.parameters = makeDispatcherModuleParameters( 300, @@ -279,10 +283,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipHotspotBuddy = new EquipHotspotBuddy(); - this.setMessageGrabData = function(entityProperties) { + this.setMessageGrabData = function(entityProperties, mouseEquip) { if (entityProperties) { this.messageGrabEntity = true; this.grabEntityProps = entityProperties; + this.mouseEquip = mouseEquip; } }; @@ -580,6 +585,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; this.messageGrabEntity = false; this.grabEntityProps = null; + this.mouseEquip = false; }; this.updateInputs = function (controllerData) { @@ -656,7 +662,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var timestamp = Date.now(); this.updateInputs(controllerData); - if (!this.isTargetIDValid(controllerData)) { + if (!this.mouseEquip && !this.isTargetIDValid(controllerData)) { this.endEquipEntity(); return makeRunningValues(false, [], []); } @@ -757,7 +763,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity; var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); entityProperties.id = data.entityID; - equipModule.setMessageGrabData(entityProperties); + var mouseEquip = false; + equipModule.setMessageGrabData(entityProperties, mouseEquip); } catch (e) { print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message); @@ -774,10 +781,88 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } } }; - + + var clearGrabActions = function(entityID) { + var actionIDs = Entities.getActionIDs(entityID); + var myGrabTag = "grab-" + MyAvatar.sessionUUID; + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments.tag; + if (tag === myGrabTag) { + Entities.deleteAction(entityID, actionID); + } + } + }; + + var onMousePress = function(event) { + if (isInEditMode() || !event.isLeftButton) { // don't consider any left clicks on the entity while in edit + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Entities.findRayIntersection(pickRay, true); + if (intersection.intersects) { + var entityID = intersection.entityID; + var entityProperties = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); + var hasEquipData = getWearableData(entityProperties).joints || getEquipHotspotsData(entityProperties).length > 0; + if (hasEquipData && entityProperties.parentID === EMPTY_PARENT_ID && !entityIsFarGrabbedByOther(entityID)) { + entityProperties.id = entityID; + var rightHandPosition = MyAvatar.getJointPosition("RightHand"); + var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); + var distanceToRightHand = Vec3.distance(entityProperties.position, rightHandPosition); + var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition); + var leftHandAvailable = leftEquipEntity.targetEntityID === null; + var rightHandAvailable = rightEquipEntity.targetEntityID === null; + var mouseEquip = true; + if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) { + // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) + clearGrabActions(entityID); + rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { + // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) + clearGrabActions(entityID); + leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + } + } + } + }; + + var onKeyPress = function(event) { + if (event.text === UNEQUIP_KEY) { + if (rightEquipEntity.targetEntityID) { + rightEquipEntity.endEquipEntity(); + } + if (leftEquipEntity.targetEntityID) { + leftEquipEntity.endEquipEntity(); + } + } + }; + + var deleteEntity = function(entityID) { + if (rightEquipEntity.targetEntityID === entityID) { + rightEquipEntity.endEquipEntity(); + } + if (leftEquipEntity.targetEntityID === entityID) { + leftEquipEntity.endEquipEntity(); + } + }; + + var clearEntities = function() { + if (rightEquipEntity.targetEntityID) { + rightEquipEntity.endEquipEntity(); + } + if (leftEquipEntity.targetEntityID) { + leftEquipEntity.endEquipEntity(); + } + }; + Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-Drop'); Messages.messageReceived.connect(handleMessage); + Controller.mousePressEvent.connect(onMousePress); + Controller.keyPressEvent.connect(onKeyPress); + Entities.deletingEntity.connect(deleteEntity); + Entities.clearingEntities.connect(clearEntities); var leftEquipEntity = new EquipEntity(LEFT_HAND); var rightEquipEntity = new EquipEntity(RIGHT_HAND); @@ -791,6 +876,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa disableDispatcherModule("LeftEquipEntity"); disableDispatcherModule("RightEquipEntity"); clearAttachPoints(); + Messages.messageReceived.disconnect(handleMessage); + Controller.mousePressEvent.disconnect(onMousePress); + Controller.keyPressEvent.disconnect(onKeyPress); + Entities.deletingEntity.disconnect(deleteEntity); + Entities.clearingEntities.disconnect(clearEntities); } Script.scriptEnding.connect(cleanup); }()); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 36dd9314bb..8ae94a4caa 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -569,7 +569,7 @@ Grabber.prototype.moveEventProcess = function() { } if (!this.actionID) { - if (!entityIsGrabbedByOther(this.entityID)) { + if (!entityIsGrabbedByOther(this.entityID) && !entityIsEquipped(this.entityID)) { this.actionID = Entities.addAction("far-grab", this.entityID, actionArgs); } } else { diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6d2b1f129b..c99c8d401a 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1291,6 +1291,7 @@ function cleanupModelMenus() { Menu.removeMenuItem("Edit", MENU_EASE_ON_FOCUS); Menu.removeMenuItem("Edit", MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE); Menu.removeMenuItem("Edit", MENU_SHOW_ZONES_IN_EDIT_MODE); + Menu.removeMenuItem("Edit", MENU_CREATE_ENTITIES_GRABBABLE); } Script.scriptEnding.connect(function () { diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 9edd6d006a..71dc5e4273 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -415,6 +415,34 @@ distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) { return Vec3.distance(v, localPoint); }; +entityIsEquipped = function(entityID) { + var rightEquipEntity = getEnabledModuleByName("RightEquipEntity"); + var leftEquipEntity = getEnabledModuleByName("LeftEquipEntity"); + var equippedInRightHand = rightEquipEntity ? rightEquipEntity.targetEntityID === entityID : false; + var equippedInLeftHand = leftEquipEntity ? leftEquipEntity.targetEntityID === entityID : false; + return equippedInRightHand || equippedInLeftHand; +}; + +entityIsFarGrabbedByOther = function(entityID) { + // by convention, a far grab sets the tag of its action to be far-grab-*owner-session-id*. + var actionIDs = Entities.getActionIDs(entityID); + var myFarGrabTag = "far-grab-" + MyAvatar.sessionUUID; + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments.tag; + if (tag == myFarGrabTag) { + // we see a far-grab-*uuid* shaped tag, but it's our tag, so that's okay. + continue; + } + if (tag.slice(0, 9) == "far-grab-") { + // we see a far-grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. + return true; + } + } + return false; +}; + if (typeof module !== 'undefined') { module.exports = { makeDispatcherModuleParameters: makeDispatcherModuleParameters, diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 64ce73fad6..a05778e2dd 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -153,40 +153,16 @@ var selectionDisplay = null; // for gridTool.js to ignore } // BEGIN AVATAR SELECTOR LOGIC - var UNSELECTED_TEXTURES = { - "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png") - }; - var SELECTED_TEXTURES = { - "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png") - }; - var HOVER_TEXTURES = { - "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"), - "idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png") - }; - var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6 }; var SELECTED_COLOR = { red: 0xF3, green: 0x91, blue: 0x29 }; var HOVER_COLOR = { red: 0xD0, green: 0xD0, blue: 0xD0 }; - var conserveResources = true; var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier. - function ExtendedOverlay(key, type, properties, selected, hasModel) { // A wrapper around overlays to store the key it is associated with. + function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with. overlays[key] = this; - if (hasModel) { - var modelKey = key + "-m"; - this.model = new ExtendedOverlay(modelKey, "model", { - url: Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx"), - textures: textures(selected), - ignoreRayIntersection: true - }, false, false); - } else { - this.model = undefined; - } this.key = key; - this.selected = selected || false; // not undefined + this.selected = false; this.hovering = false; this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected... } @@ -208,10 +184,6 @@ var selectionDisplay = null; // for gridTool.js to ignore } return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) }; } - - function textures(selected, hovering) { - return hovering ? HOVER_TEXTURES : selected ? SELECTED_TEXTURES : UNSELECTED_TEXTURES; - } // so we don't have to traverse the overlays to get the last one var lastHoveringId = 0; ExtendedOverlay.prototype.hover = function (hovering) { @@ -223,9 +195,6 @@ var selectionDisplay = null; // for gridTool.js to ignore lastHoveringId = 0; } this.editOverlay({ color: color(this.selected, hovering) }); - if (this.model) { - this.model.editOverlay({ textures: textures(this.selected, hovering) }); - } if (hovering) { // un-hover the last hovering overlay if (lastHoveringId && lastHoveringId !== this.key) { @@ -240,15 +209,12 @@ var selectionDisplay = null; // for gridTool.js to ignore } this.editOverlay({ color: color(selected, this.hovering) }); - if (this.model) { - this.model.editOverlay({ textures: textures(selected) }); - } this.selected = selected; }; // Class methods: - var selectedIds = []; + var selectedId = false; ExtendedOverlay.isSelected = function (id) { - return -1 !== selectedIds.indexOf(id); + return selectedId === id; }; ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier return overlays[key]; @@ -285,51 +251,14 @@ var selectionDisplay = null; // for gridTool.js to ignore }); }; - function HighlightedEntity(id, entityProperties) { - this.id = id; - this.overlay = Overlays.addOverlay('cube', { - position: entityProperties.position, - rotation: entityProperties.rotation, - dimensions: entityProperties.dimensions, - solid: false, - color: { - red: 0xF3, - green: 0x91, - blue: 0x29 - }, - ignoreRayIntersection: true, - drawInFront: false // Arguable. For now, let's not distract with mysterious wires around the scene. - }); - HighlightedEntity.overlays.push(this); - } - HighlightedEntity.overlays = []; - HighlightedEntity.clearOverlays = function clearHighlightedEntities() { - HighlightedEntity.overlays.forEach(function (highlighted) { - Overlays.deleteOverlay(highlighted.overlay); - }); - HighlightedEntity.overlays = []; - }; - HighlightedEntity.updateOverlays = function updateHighlightedEntities() { - HighlightedEntity.overlays.forEach(function (highlighted) { - var properties = Entities.getEntityProperties(highlighted.id, ['position', 'rotation', 'dimensions']); - Overlays.editOverlay(highlighted.overlay, { - position: properties.position, - rotation: properties.rotation, - dimensions: properties.dimensions - }); - }); - }; - - function addAvatarNode(id) { - var selected = ExtendedOverlay.isSelected(id); return new ExtendedOverlay(id, "sphere", { drawInFront: true, solid: true, alpha: 0.8, - color: color(selected, false), + color: color(false, false), ignoreRayIntersection: false - }, selected, !conserveResources); + }); } var pingPong = true; @@ -368,14 +297,6 @@ var selectionDisplay = null; // for gridTool.js to ignore position: target, dimensions: 0.032 * distance }); - if (overlay.model) { - overlay.model.ping = pingPong; - overlay.model.editOverlay({ - position: target, - scale: 0.2 * distance, // constant apparent size - rotation: Camera.orientation - }); - } }); pingPong = !pingPong; ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.) @@ -383,13 +304,10 @@ var selectionDisplay = null; // for gridTool.js to ignore overlay.deleteOverlay(); } }); - // We could re-populateNearbyUserList if anything added or removed, but not for now. - HighlightedEntity.updateOverlays(); } function removeOverlays() { - selectedIds = []; + selectedId = false; lastHoveringId = 0; - HighlightedEntity.clearOverlays(); ExtendedOverlay.some(function (overlay) { overlay.deleteOverlay(); }); @@ -399,7 +317,7 @@ var selectionDisplay = null; // for gridTool.js to ignore // Clicks. // function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { - if (selectedIds[0] === id) { + if (selectedId === id) { var message = { method: 'updateSelectedRecipientUsername', userName: username === "" ? "unknown username" : username @@ -411,13 +329,13 @@ var selectionDisplay = null; // for gridTool.js to ignore ExtendedOverlay.applyPickRay(pickRay, function (overlay) { var nextSelectedStatus = !overlay.selected; var avatarId = overlay.key; - selectedIds = nextSelectedStatus ? [avatarId] : []; + selectedId = nextSelectedStatus ? avatarId : false; if (nextSelectedStatus) { Users.requestUsernameFromID(avatarId); } var message = { method: 'selectRecipient', - id: [avatarId], + id: avatarId, isSelected: nextSelectedStatus, displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"', userName: '' @@ -430,24 +348,6 @@ var selectionDisplay = null; // for gridTool.js to ignore overlay.select(selected); }); - HighlightedEntity.clearOverlays(); - if (selectedIds.length) { - Entities.findEntitiesInFrustum(Camera.frustum).forEach(function (id) { - // Because lastEditedBy is per session, the vast majority of entities won't match, - // so it would probably be worth reducing marshalling costs by asking for just we need. - // However, providing property name(s) is advisory and some additional properties are - // included anyway. As it turns out, asking for 'lastEditedBy' gives 'position', 'rotation', - // and 'dimensions', too, so we might as well make use of them instead of making a second - // getEntityProperties call. - // It would be nice if we could harden this against future changes by specifying all - // and only these four in an array, but see - // https://highfidelity.fogbugz.com/f/cases/2728/Entities-getEntityProperties-id-lastEditedBy-name-lastEditedBy-doesn-t-work - var properties = Entities.getEntityProperties(id, 'lastEditedBy'); - if (ExtendedOverlay.isSelected(properties.lastEditedBy)) { - new HighlightedEntity(id, properties); - } - }); - } return true; }); } @@ -1155,12 +1055,14 @@ var selectionDisplay = null; // for gridTool.js to ignore } if (onCommerceScreen) { + if (!isWired) { + Users.usernameFromIDReply.connect(usernameFromIDReply); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + triggerMapping.enable(); + triggerPressMapping.enable(); + } isWired = true; - Users.usernameFromIDReply.connect(usernameFromIDReply); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - triggerMapping.enable(); - triggerPressMapping.enable(); Wallet.refreshWalletStatus(); } else { off(); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index c24e34cc1b..0a01007ee9 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -715,7 +715,6 @@ function onTabletScreenChanged(type, url) { ContextOverlay.enabled = false; Users.requestsDomainListData = true; - populateNearbyUserList(); audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); @@ -726,6 +725,7 @@ function onTabletScreenChanged(type, url) { Users.usernameFromIDReply.connect(usernameFromIDReply); triggerMapping.enable(); triggerPressMapping.enable(); + populateNearbyUserList(); } else { off(); ContextOverlay.enabled = true; diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index 3598f30ee0..099c4be9e3 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -321,7 +321,8 @@ { id: "alpha", name: "Alpha", - type: "SliderFloat" + type: "SliderFloat", + max: 1.0 }, { type: "Row" @@ -329,7 +330,8 @@ { id: "alphaSpread", name: "Alpha Spread", - type: "SliderFloat" + type: "SliderFloat", + max: 1.0 }, { type: "Row" @@ -337,7 +339,8 @@ { id: "alphaStart", name: "Alpha Start", - type: "SliderFloat" + type: "SliderFloat", + max: 1.0 }, { type: "Row" @@ -345,7 +348,8 @@ { id: "alphaFinish", name: "Alpha Finish", - type: "SliderFloat" + type: "SliderFloat", + max: 1.0 }, { type: "Row" diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt index b5e866ccce..ce1c150ed4 100644 --- a/tests/controllers/CMakeLists.txt +++ b/tests/controllers/CMakeLists.txt @@ -23,5 +23,9 @@ if (WIN32) add_dependency_external_projects(wasapi) endif() +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + target_link_libraries(${TARGET_NAME} atomic) +endif() + package_libraries_for_deployment() -endif() \ No newline at end of file +endif() diff --git a/tests/gpu-test/src/TestWindow.cpp b/tests/gpu-test/src/TestWindow.cpp index 1fa4862e53..f667f20f2b 100644 --- a/tests/gpu-test/src/TestWindow.cpp +++ b/tests/gpu-test/src/TestWindow.cpp @@ -98,7 +98,7 @@ void TestWindow::beginFrame() { _preparePrimaryFramebuffer.run(_renderContext, primaryFramebuffer); DeferredFrameTransformPointer frameTransform; - _generateDeferredFrameTransform.run(_renderContext, frameTransform); + _generateDeferredFrameTransform.run(_renderContext, glm::vec2(0.0f, 0.0f), frameTransform); LightingModelPointer lightingModel; _generateLightingModel.run(_renderContext, lightingModel); diff --git a/tests/qml/qml/controls/WebEntityView.qml b/tests/qml/qml/controls/WebEntityView.qml new file mode 100644 index 0000000000..5bd29ef457 --- /dev/null +++ b/tests/qml/qml/controls/WebEntityView.qml @@ -0,0 +1,47 @@ +// +// WebEntityView.qml +// +// Created by Kunal Gosar on 16 March 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 QtWebEngine 1.5 +import Hifi 1.0 + +/* +TestItem { + Rectangle { + anchors.fill: parent + anchors.margins: 10 + color: "blue" + property string url: "" + ColorAnimation on color { + loops: Animation.Infinite; from: "blue"; to: "yellow"; duration: 1000 + } + } +} +*/ + +WebEngineView { + id: webViewCore + objectName: "webEngineView" + width: parent !== null ? parent.width : undefined + height: parent !== null ? parent.height : undefined + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + //disable popup + onContextMenuRequested: { + request.accepted = true; + } + + onNewViewRequested: { + newViewRequestedCallback(request) + } +} diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index 022f7290f4..349ac55d88 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -28,52 +28,87 @@ #include #include #include - +#include #include #include +#include + #include -#include #include #include #include #include #include #include +#include +#include +namespace gl { +extern void initModuleGl(); +} -class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { +class QTestItem : public QQuickItem { + Q_OBJECT +public: + QTestItem(QQuickItem* parent = nullptr) : QQuickItem(parent) { qDebug() << __FUNCTION__; } + ~QTestItem() { qDebug() << __FUNCTION__; } +}; + +QUrl getTestResource(const QString& relativePath) { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Resources Path: " << dir; + } + return QUrl::fromLocalFile(dir + relativePath); +} + +#define DIVISIONS_X 5 +#define DIVISIONS_Y 5 + +using QmlPtr = QSharedPointer; +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; + +struct QmlInfo { + QmlPtr surface; + GLuint texture{ 0 }; + uint64_t lifetime{ 0 }; }; class TestWindow : public QWindow { - public: TestWindow(); - private: - using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; QOpenGLContext _glContext; OffscreenGLCanvas _sharedContext; - OffscreenQmlSurface _offscreenQml; + std::array, DIVISIONS_X> _surfaces; + QOpenGLFunctions_4_5_Core _glf; - uint32_t _currentTexture{ 0 }; - GLsync _readFence{ 0 }; std::function _discardLamdba; QSize _size; + size_t _surfaceCount{ 0 }; GLuint _fbo{ 0 }; const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; + uint64_t _createStopTime; void initGl(); + void updateSurfaces(); + void buildSurface(QmlInfo& qmlInfo, bool allowVideo); + void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); void resizeEvent(QResizeEvent* ev) override; }; TestWindow::TestWindow() { - setSurfaceType(QSurface::OpenGLSurface); + Setting::init(); + setSurfaceType(QSurface::OpenGLSurface); QSurfaceFormat format; format.setDepthBufferSize(24); format.setStencilBufferSize(8); @@ -83,13 +118,16 @@ TestWindow::TestWindow() { QSurfaceFormat::setDefaultFormat(format); setFormat(format); + qmlRegisterType("Hifi", 1, 0, "TestItem"); + show(); + _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); resize(QSize(800, 600)); auto timer = new QTimer(this); timer->setTimerType(Qt::PreciseTimer); - timer->setInterval(5); + timer->setInterval(30); connect(timer, &QTimer::timeout, [&] { draw(); }); timer->start(); @@ -97,7 +135,6 @@ TestWindow::TestWindow() { timer->stop(); _aboutToQuit = true; }); - } void TestWindow::initGl() { @@ -105,6 +142,7 @@ void TestWindow::initGl() { if (!_glContext.create() || !_glContext.makeCurrent(this)) { qFatal("Unable to intialize Window GL context"); } + gl::initModuleGl(); _glf.initializeOpenGLFunctions(); _glf.glCreateFramebuffers(1, &_fbo); @@ -113,15 +151,97 @@ void TestWindow::initGl() { qFatal("Unable to intialize Shared GL context"); } hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext()); - _discardLamdba = _offscreenQml.getDiscardLambda(); - _offscreenQml.resize({ 640, 480 }); - _offscreenQml.load(QUrl::fromLocalFile("C:/Users/bdavi/Git/hifi/tests/qml/qml/main.qml")); + _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); } void TestWindow::resizeWindow(const QSize& size) { _size = size; } +static const int DEFAULT_MAX_FPS = 10; +static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; +static const char* URL_PROPERTY{ "url" }; + +QString getSourceUrl(bool video) { + static const std::vector SOURCE_URLS{ + "https://www.reddit.com/wiki/random", + "https://en.wikipedia.org/wiki/Wikipedia:Random", + "https://slashdot.org/", + }; + + static const std::vector VIDEO_SOURCE_URLS{ + "https://www.youtube.com/watch?v=gDXwhHm4GhM", + "https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; + auto index = rand() % sourceUrls.size(); + return sourceUrls[index]; +} + +void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) { + ++_surfaceCount; + auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); + auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); + qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); + qmlInfo.texture = 0; + qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); + qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl(video)); + }); + qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); + qmlInfo.surface->resize(_qmlSize); + qmlInfo.surface->resume(); +} + +void TestWindow::destroySurface(QmlInfo& qmlInfo) { + auto& surface = qmlInfo.surface; + auto webView = surface->getRootItem(); + if (webView) { + // stop loading + QMetaObject::invokeMethod(webView, "stop"); + webView->setProperty(URL_PROPERTY, "about:blank"); + } + surface->pause(); + surface.reset(); +} + +void TestWindow::updateSurfaces() { + auto now = usecTimestampNow(); + // Fetch any new textures + for (size_t x = 0; x < DIVISIONS_X; ++x) { + for (size_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface) { + if (now < _createStopTime && randFloat() > 0.99f) { + buildSurface(qmlInfo, x == 0 && y == 0); + } else { + continue; + } + } + + if (now > qmlInfo.lifetime) { + destroySurface(qmlInfo); + continue; + } + + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + + TextureAndFence newTextureAndFence; + if (surface->fetchTexture(newTextureAndFence)) { + if (currentTexture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + currentTexture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } + } + } +} + void TestWindow::draw() { if (_aboutToQuit) { return; @@ -140,38 +260,30 @@ void TestWindow::draw() { return; } + updateSurfaces(); + + auto size = this->geometry().size(); + auto incrementX = size.width() / DIVISIONS_X; + auto incrementY = size.height() / DIVISIONS_Y; + _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - - TextureAndFence newTextureAndFence; - if (_offscreenQml.fetchTexture(newTextureAndFence)) { - if (_currentTexture) { - _discardLamdba(_currentTexture, _readFence); - _readFence = 0; + for (uint32_t x = 0; x < DIVISIONS_X; ++x) { + for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface || !qmlInfo.texture) { + continue; + } + _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, qmlInfo.texture, 0); + _glf.glBlitNamedFramebuffer(_fbo, 0, + // src coordinates + 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, + // dst coordinates + incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); } - - _currentTexture = newTextureAndFence.first; - _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); - _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, _currentTexture, 0); } - - auto diff = _size - _qmlSize; - diff /= 2; - auto qmlExtent = diff + _qmlSize; - - if (_currentTexture) { - _glf.glBlitNamedFramebuffer(_fbo, 0, - 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, - diff.width(), diff.height(), qmlExtent.width() - 1, qmlExtent.height() - 2, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - - if (_readFence) { - _glf.glDeleteSync(_readFence); - } - _readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - _glf.glFlush(); - _glContext.swapBuffers(this); } @@ -180,11 +292,9 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { } int main(int argc, char** argv) { - setupHifiApplication("QML Test"); - QGuiApplication app(argc, argv); TestWindow window; - app.exec(); - return 0; + return app.exec(); } +#include "main.moc" diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index fd4d8d88dd..d688474379 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -27,6 +27,10 @@ if (WIN32) add_dependency_external_projects(wasapi) endif() +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + target_link_libraries(${TARGET_NAME} atomic) +endif() + package_libraries_for_deployment() diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 93672cc5a2..9249b3d957 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -453,8 +453,8 @@ protected: return vec3(); } - bool isAboutToQuit() const override { return false; } - void postLambdaEvent(std::function f) override {} + void postLambdaEvent(const std::function& f) override {} + void sendLambdaEvent(const std::function& f) override {} qreal getDevicePixelRatio() override { return 1.0f; @@ -469,7 +469,7 @@ protected: } std::map> _postUpdateLambdas; - void pushPostUpdateLambda(void* key, std::function func) override { + void pushPostUpdateLambda(void* key, const std::function& func) override { _postUpdateLambdas[key] = func; } diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 61cd0d2792..5092e8b809 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -9,20 +9,34 @@ function endsWith(path, exts) { } exports.handlers = { + + // This event is triggered before parsing has even started. + // We use this event to scan the C++ files for jsdoc comments + // and reformat them into a form digestable by jsdoc. beforeParse: function(e) { - const pathTools = require('path'); + var pathTools = require('path'); var rootFolder = pathTools.dirname(e.filename); console.log("Scanning hifi source for jsdoc comments..."); // directories to scan for jsdoc comments var dirList = [ '../../interface/src', + '../../interface/src/assets', + '../../interface/src/audio', '../../interface/src/avatar', + '../../interface/src/commerce', + '../../interface/src/devices', + '../../interface/src/java', + '../../interface/src/networking', + '../../interface/src/ui/', '../../interface/src/scripting', '../../interface/src/ui/overlays', '../../interface/src/raypick', '../../libraries/animation/src', + '../../libraries/audio-client/src', + '../../libraries/audio/src', '../../libraries/avatars/src', + '../../libraries/avatars-renderer/src/avatars-renderer', '../../libraries/controllers/src/controllers/', '../../libraries/controllers/src/controllers/impl/', '../../libraries/display-plugins/src/display-plugins/', @@ -37,26 +51,96 @@ exports.handlers = { '../../libraries/script-engine/src', '../../libraries/shared/src', '../../libraries/shared/src/shared', + '../../libraries/trackers/src/trackers', + '../../libraries/ui/src/ui', '../../plugins/oculus/src', - '../../plugins/openvr/src', + '../../plugins/openvr/src' ]; + + // only files with this extension will be searched for jsdoc comments. var exts = ['.h', '.cpp']; - const fs = require('fs'); + var fs = require('fs'); dirList.forEach(function (dir) { var joinedDir = pathTools.join(rootFolder, dir); - var files = fs.readdirSync(joinedDir) + var files = fs.readdirSync(joinedDir); files.forEach(function (file) { var path = pathTools.join(joinedDir, file); if (fs.lstatSync(path).isFile() && endsWith(path, exts)) { + // load entire file into a string var data = fs.readFileSync(path, "utf8"); + + // this regex searches for blocks starting with /**jsdoc and end with */ var reg = /(\/\*\*jsdoc(.|[\r\n])*?\*\/)/gm; var matches = data.match(reg); if (matches) { - e.source += matches.map(function (s) { return s.replace('/**jsdoc', '/**'); }).join('\n'); + // add to source, but strip off c-comment asterisks + e.source += matches.map(function (s) { + return s.replace('/**jsdoc', '/**'); + }).join('\n'); } } }); }); + }, + + // This event is triggered when a new doclet has been created + // but before it is passed to the template for output + newDoclet: function (e) { + + // we only care about hifi custom tags on namespace and class doclets + if (e.doclet.kind === "namespace" || e.doclet.kind === "class") { + var rows = []; + if (e.doclet.hifiInterface) { + rows.push("Interface Scripts"); + } + if (e.doclet.hifiClientEntity) { + rows.push("Client Entity Scripts"); + } + if (e.doclet.hifiServerEntity) { + rows.push("Server Entity Scripts"); + } + if (e.doclet.hifiAssignmentClient) { + rows.push("Assignment Client Scripts"); + } + + // Append an Available In: table at the end of the namespace description. + if (rows.length > 0) { + var table = "

    Available in:" + rows.join("") + "
    "; + e.doclet.description = (e.doclet.description ? e.doclet.description : "") + table; + } + } } }; + +// Define custom hifi tags here +exports.defineTags = function (dictionary) { + + // @hifi-interface + dictionary.defineTag("hifi-interface", { + onTagged: function (doclet, tag) { + doclet.hifiInterface = true; + } + }); + + // @hifi-assignment-client + dictionary.defineTag("hifi-assignment-client", { + onTagged: function (doclet, tag) { + doclet.hifiAssignmentClient = true; + } + }); + + // @hifi-client-entity + dictionary.defineTag("hifi-client-entity", { + onTagged: function (doclet, tag) { + doclet.hifiClientEntity = true; + } + }); + + // @hifi-server-entity + dictionary.defineTag("hifi-server-entity", { + onTagged: function (doclet, tag) { + doclet.hifiServerEntity = true; + } + }); +};