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.
+ * 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.
+ * 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
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 Name
Description
Example
+ *
+ *
+ *
+ *
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.
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.
+ *
+ *
+ *
releaseGrab
+ *
This 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}.
+ * @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.
+ *
+ *
+ * // 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.
+ *
+ * // 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.
+ *
+ * // 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.
+ *
+ *
+ * // 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
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